diff --git a/README.md b/README.md
index 76601f34..8da99532 100644
--- a/README.md
+++ b/README.md
@@ -345,3 +345,11 @@ add_filter('optml_dont_replace_url', function( $old, $url ) {
}, 10, 2);
+
+= Can I keep the sizes attribute I wrote for an image ? =
+
+Yes. Add the `data-optml-keep-sizes` attribute to the image tag and the page profiler won't add its own entries to the image's `sizes` attribute. Images lazy loaded by Optimole don't keep `srcset` or `sizes`, since the lazy loader doesn't need them. To keep the existing `sizes` attribute on every image, add this snippet:
+
+
+add_filter( 'optml_keep_existing_sizes', '__return_true' );
+
diff --git a/inc/tag_replacer.php b/inc/tag_replacer.php
index 3330edb2..0df7d581 100644
--- a/inc/tag_replacer.php
+++ b/inc/tag_replacer.php
@@ -13,6 +13,11 @@ final class Optml_Tag_Replacer extends Optml_App_Replacer {
use Optml_Validator;
use Optml_Dam_Offload_Utils;
+ /**
+ * Attribute an image uses to keep its own sizes attribute.
+ */
+ const KEEP_SIZES_ATTRIBUTE = 'data-optml-keep-sizes';
+
/**
* Cached object instance.
*
@@ -550,11 +555,16 @@ public function add_missing_srcset_attributes( $tag, $missing_srcsets, $new_url,
$tag = $this->insert_after_attribute( $tag, 'src', $srcset_attr );
}
- // Handle sizes attribute - skip if existing sizes contains calc() or complex formulas
+ // Handle sizes attribute - skip if the image asks to keep its existing sizes, or they contain calc() or complex formulas
if ( ! empty( $new_sizes_entries ) ) {
$should_skip_sizes = false;
- if ( $has_existing_sizes ) {
+ if ( $has_existing_sizes && $this->keeps_existing_sizes( $tag ) ) {
+ $should_skip_sizes = true;
+ if ( OPTML_DEBUG ) {
+ do_action( 'optml_log', 'Keeping the existing sizes attribute: ' . $new_url );
+ }
+ } elseif ( $has_existing_sizes ) {
// Check if existing sizes contains calc() or complex formulas
$sizes = $this->find_quoted_attribute( $tag, 'sizes' );
if ( null !== $sizes ) {
@@ -596,9 +606,9 @@ function ( $a, $b ) {
}
}
} else {
- // Log that sizes were skipped due to complex formulas
+ // Log that sizes were skipped (kept on request, or complex formula)
if ( OPTML_DEBUG ) {
- do_action( 'optml_log', 'Skipped adding sizes due to complex formula detection' );
+ do_action( 'optml_log', 'Skipped adding sizes' );
}
}
}
@@ -834,6 +844,28 @@ private function has_attribute( string $tag, string $name ): bool {
return in_array( $name, array_column( $this->get_attributes( $tag ) ?? [], 'name' ), true );
}
+ /**
+ * Check whether an image's own sizes attribute must be left as it is.
+ *
+ * An image opts out with the data-optml-keep-sizes attribute. The optml_keep_existing_sizes filter
+ * does it for every image, or for the ones it picks, with no change to the markup.
+ *
+ * @param string $tag The image tag.
+ *
+ * @return bool True if the existing sizes attribute is kept.
+ */
+ private function keeps_existing_sizes( string $tag ): bool {
+ $keep = $this->has_attribute( $tag, self::KEEP_SIZES_ATTRIBUTE );
+
+ /**
+ * Filters whether Optimole leaves an image's existing sizes attribute unchanged.
+ *
+ * @param bool $keep Whether the existing sizes attribute is kept. True when the image has data-optml-keep-sizes.
+ * @param string $tag The image tag.
+ */
+ return (bool) apply_filters( 'optml_keep_existing_sizes', $keep, $tag );
+ }
+
/**
* Check whether the tag's src or srcset is a data: URI placeholder.
*
diff --git a/readme.txt b/readme.txt
index 89ca3fab..da7c72b0 100755
--- a/readme.txt
+++ b/readme.txt
@@ -345,3 +345,11 @@ add_filter('optml_dont_replace_url', function( $old, $url ) {
}, 10, 2);
+
+= Can I keep the sizes attribute I wrote for an image ? =
+
+Yes. Add the `data-optml-keep-sizes` attribute to the image tag and the page profiler won't add its own entries to the image's `sizes` attribute. Images lazy loaded by Optimole don't keep `srcset` or `sizes`, since the lazy loader doesn't need them. To keep the existing `sizes` attribute on every image, add this snippet:
+
+
+add_filter( 'optml_keep_existing_sizes', '__return_true' );
+
diff --git a/tests/test-srcset.php b/tests/test-srcset.php
index 5b90d7d6..3f8c6b3d 100644
--- a/tests/test-srcset.php
+++ b/tests/test-srcset.php
@@ -168,6 +168,65 @@ public function test_add_missing_srcset_attributes_with_existing_sizes() {
$this->assertStringContainsString( '(max-width: 768px) 400px', $result );
}
+ /**
+ * An image with data-optml-keep-sizes keeps its hand-written sizes, and still gets the new srcset entries.
+ */
+ public function test_keep_sizes_attribute_preserves_existing_sizes() {
+ $tag_replacer = new Optml_Tag_Replacer();
+
+ $tag = '
';
+ $missing_srcsets = [
+ [ 'w' => 200, 'h' => 150, 'd' => 1, 's' => '200w', 'b' => 320 ],
+ [ 'w' => 400, 'h' => 300, 'd' => 1, 's' => '400w', 'b' => 768 ],
+ ];
+
+ $result = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false );
+
+ $this->assertStringContainsString( 'sizes="(max-width: 480px) 300px"', $result );
+ $this->assertStringNotContainsString( '(max-width: 320px) 200px', $result );
+ $this->assertStringContainsString( '200w', $result );
+ }
+
+ /**
+ * The optml_keep_existing_sizes filter keeps existing sizes site-wide, with no change to the markup.
+ */
+ public function test_keep_existing_sizes_filter() {
+ $tag_replacer = new Optml_Tag_Replacer();
+
+ $tag = '
';
+ $missing_srcsets = [
+ [ 'w' => 200, 'h' => 150, 'd' => 1, 's' => '200w', 'b' => 320 ],
+ ];
+
+ add_filter( 'optml_keep_existing_sizes', '__return_true' );
+ $result = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false );
+ remove_filter( 'optml_keep_existing_sizes', '__return_true' );
+
+ $this->assertStringContainsString( 'sizes="(max-width: 480px) 300px"', $result );
+ $this->assertStringNotContainsString( '(max-width: 320px) 200px', $result );
+ }
+
+ /**
+ * A filter returning false overrides the attribute, so the existing sizes are enhanced again.
+ */
+ public function test_keep_existing_sizes_filter_can_override_attribute() {
+ $tag_replacer = new Optml_Tag_Replacer();
+
+ $tag = '
';
+ $missing_srcsets = [
+ [ 'w' => 200, 'h' => 150, 'd' => 1, 's' => '200w', 'b' => 320 ],
+ ];
+
+ $kept = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false );
+ $this->assertStringNotContainsString( '(max-width: 320px) 200px', $kept );
+
+ add_filter( 'optml_keep_existing_sizes', '__return_false' );
+ $result = $tag_replacer->add_missing_srcset_attributes( $tag, $missing_srcsets, 'https://example.com/image.jpg', false );
+ remove_filter( 'optml_keep_existing_sizes', '__return_false' );
+
+ $this->assertStringContainsString( '(max-width: 320px) 200px', $result );
+ }
+
/**
* Test retina images setting respect
*/