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 = 'Test'; + $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 = 'Test'; + $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 = 'Test'; + $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 */