WordPress.org

Make WordPress Core

Opened 2 months ago

Last modified 12 days ago

#53232 new feature request

Add async decoding attribute as default to the HTML image tags generated by WordPress

Reported by: isaumya Owned by:
Milestone: 5.9 Priority: normal
Severity: normal Version:
Component: Media Keywords: needs-patch
Focuses: performance Cc:

Description

Hi,
WordPress has taken advantage of many amazing features to the image tags like srcset, sizes and recently loading attributes. Thanks to the loading="lazy" attribute all images generated by WordPress system have that attribute in it by default. This is insanely helpful to make page load faster and more responsive.

Recently I have been playing around with another special attribute to the img tag which is basically decoding="async". After implementing async decoding to the images in a page, the page load became, even more, faster and images are decoded asynchronously by the browser, loading the contents almost instantly and also reducing page render time. This is a huge performance booster to any webpages which has a lot of images (so basically most sites).

This decoding attribute also has great browser support according to MDN docs.

While trying to implement the feature on a WordPress site, with the help of the following code snippet I was able to make it work across all the images on the website except for the images added via Gutenberg.

<?php
add_filter( 'wp_get_attachment_image_attributes', function($attr) {
  $attr['decoding'] = 'async';

  return $attr;
} );

As Gutenberg doesn't support either image_send_to_editor filter or wp_get_attachment_image_attributes filter. Adding it to Gutenberg generated images are more complicated and includes overwriting many core blocks like images, gallery etc.

But I was wondering if the WP Core team consider adding this decoding="async" attribute to the images like loading="lazy" as it will help a lot of people, making their webpage render faster by browsers.

Change History (16)

#1 @westonruter
2 months ago

Thanks for filing this. I was actually asking if anyone has raised this yet and I was going to file it, but then you beat me to it.

The use of decoding=async is deemed a best practice to according to Malte Ubl:

Adding decoding="async" to the img gives the browser permission to decode the image off the main thread avoiding user impact of the CPU-time used to decode the image. This should have no discernible downside except that it cannot always be the default for legacy reasons.

I'm not clear on the specifics of the legacy reasons. I think it may have to do with if you try to load an image with JavaScript and then try to access its pixels. With async decoding, you then have to use the decode method to get a promise for when it is decoded.

It's also a best practice according to Addy Osmani.

In AMP all images also get decoding=async for the performance benefit.

In Next.js, the Image component uses decoding=async.

I don't see any downsides to adding decoding=async to all images, even those that we don't currently add loading=lazy. Basically, anywhere that a srcset can be supplied we can also inject decoding=async.

#2 @isaumya
2 months ago

You are absolutely right @westonruter. Haha! It's funny, I also asked in the Slack core channel if anyone has a trac ticket open about decoding="async" and no one replied to me. So, I thought, let's open one, and if there is already an existing ticket about this, at most it will get closed/merged with that ticket. I even tweeted about it.

I'm not clear on the specifics of the legacy reasons. I think it may have to do with if you try to load an image with JavaScript and then try to access its pixels. With async decoding, you then have to use the decode method to get a promise for when it is decoded.

I think you are right. But this still should be the default, as there is no easy way to add it to Gutenberg generated images as I stated above.

I'm not clear on the specifics of the legacy reasons. I think it may have to do with if you try to load an image with JavaScript and then try to access its pixels. With async decoding, you then have to use the decode method to get a promise for when it is decoded.

I think it would be beneficial even if there is a single image and no srcset and a single image url. Don't you think?

#3 follow-up: @ayeshrajans
2 months ago

This will be a nice one, thank you for opening the ticket.

I took a quick look at how lazy loading is currently implemented, and I think we should follow suit:

  • Add wp_img_tag_add_decoding_attr filter akin wp_img_tag_add_loading_attr to determine offer attribute value.
  • Add wp_img_tag_add_decoding_attr function akin wp_img_tag_add_loading_attr to make the actual change.
  • Update wp_filter_content_tags to call wp_img_tag_add_decoding_attr.
  • Make sure kses allow-lists the decoding attribute.
  • Add test coverage.

I would suggest to only add the decoding attribute only if loading=lazy is also present. This might(?) reduce sudden content reflows.

#4 @isaumya
2 months ago

Hi @ayeshrajans,
Thanks for the +1. I Googled about those filter you mentioned but couldn't find any documentation about it. Do you have any snippet to currently add it in the Gutenberg generated images? As the code block I provided above won't work for any Gutenberg images and there is no image_send_to_editor for Gutenberg, so I couldn't add it for Gutenberg generated pages.

If you have a working code snippet that will help a lot of people to add it right now to their WP sites until this gets added to the core code.

#5 in reply to: ↑ 3 ; follow-up: @westonruter
2 months ago

Replying to ayeshrajans:

Make sure kses allow-lists the decoding attribute.

This wouldn't be necessary since the decoding attribute is added on the frontend and is not stored in the post content. The loading attribute is not stored in post content either. Nevertheless, I see loading was explicitly added to Kses so might as well add it.

I would suggest to only add the decoding attribute only if loading=lazy is also present. This might(?) reduce sudden content reflows.

I don't believe this is necessary. I believe the layout shifting issue is only relevant to lazy-loading.

I took a quick look at how lazy loading is currently implemented, and I think we should follow suit:

Otherwise, your proposal for how to implement seems good to me!

Replying to isaumya:

If you have a working code snippet that will help a lot of people to add it right now to their WP sites until this gets added to the core code.

This will do the trick for all images in the post content:

<?php
add_filter(
        'the_content',
        static function ( $content ) {
                return str_replace( '<img ', '<img decoding=async ', $content );
        }
);

#6 in reply to: ↑ 5 @isaumya
2 months ago

Thanks a ton, @westonruter for the snippet. Actually, I was trying to avoid using the_content filter and instead was looking for a way to directly hook into the images rather than parsing the whole content. As when using the_content if the content is very large, PHP has to do really a lot of work executing and parsing it. I just thought using the_content was not an optimized code.

#7 @westonruter
2 months ago

Well, using the_content is how the loading attribute is added. It's how core adds srcset and sizes as well. The performance impact should be negligible. True it will be faster when when/if the replacement is done inside of wp_filter_content_tags() since there is less content to search through, but in the meantime a simple the_content filter will get you decoding=async now.

#8 @westonruter
2 months ago

  • Keywords needs-patch added
  • Milestone changed from Awaiting Review to 5.8

Another explanation on the benefits of decoding=async from Matt Fantinel:

decoding="async" tells your browser it can try to parallelize loading your image. When your page is loading, it tries to decode both text and images at the same time. On lower-end devices though, decoding heavy images can take a while, and this might block the rendering of the rest of the content. With this option, the browser will try to proceed rendering the rest of the content and render the image later. This can be a great improvement to perceived performance.

I'm going to milestone this for 5.8 even though we're nearing the May 25th date for the feature freeze, although the patch should be small. This will probably get punted to 5.9.

#9 @westonruter
2 months ago

I'm checking with some colleagues who focus on web performance and it may actually be preferable to not add decoding=async to images in the first viewport. So it may indeed be that decoding=async should only be added if loading=lazy is also being added.

#10 @westonruter
2 months ago

OK, I checked with Malte Ubl and he says:

Another highly relevant nuance:
decoding=async does ~nothing on a reasonably sized image, but it is impactful on the occasional 20 Megapixel outlier. That page will still load slow but the UI thread won't be impacted.

If you know that you're appropriately sizing images, then it doesn't really matter what value the decoding attribute has. But if you don't know (e.g. you are WordPress and you unfortunately don't have image optimization build in), then async decoding helps prevent outlier bad performance while having effectively no impact on the average page.

Therefore, this does seem relevant to add to all images in WordPress since it is relatively common for oversized images to be served, where image smaller sizes may fail to be generated for the srcset. Granted, the chance of a 20MB image being added to a post is greatly reduced since WordPress 5.3 improved handling of big images. Nevertheless, I'm not sure the big image handling is always working. I just tried adding a 90MB image post and it was added to the post without reduced size or other image sizes being created. There probably wasn't enough memory available to do any manipulation of the image at all. So this shows that decoding=async is a useful protection to have enabled by default in WordPress.

#11 @isaumya
2 months ago

Thanks a ton, @westonruter for all the in-depth analysis on this matter. It seems more impactful than I initially thought, for most WordPress sites especially the sites using page builders. Improper image size is super common in WP page builder world.

#12 @westonruter
2 months ago

  • Milestone changed from 5.8 to Future Release

#13 @isaumya
2 months ago

Hey @westonruter, so 5.9 or maybe later to that?

#14 @westonruter
2 months ago

Yeah, 5.9. The feature freeze for 5.8 is tomorrow, so it seems too late to include for this release.

#15 @westonruter
8 weeks ago

Aside: HTTP Archive just added tracking for usage of the decoding attribute on images: https://github.com/HTTPArchive/legacy.httparchive.org/pull/206

#16 @westonruter
12 days ago

  • Milestone changed from Future Release to 5.9

This makes sense to do in 5.9 alongside #53675.

Note: See TracTickets for help on using tickets.