GoogleChrome / lighthouse

Automated auditing, performance metrics, and best practices for the web.
https://developer.chrome.com/docs/lighthouse/overview/
Apache License 2.0
28.4k stars 9.38k forks source link

Responsive LCP image penalized for lazy-loading by lcp-lazy-loaded audit #16034

Open westonruter opened 5 months ago

westonruter commented 5 months ago

FAQ

URL

https://comparing-lcp-images-with-lazy-loading-and-fetchpriority-high.glitch.me/lazy-loaded-lcp-image-with-fetchpriority-high-preload-link.html

What happened?

Consider a page which shows a different image depending on whether it is a desktop or mobile device. In order to prevent both images from being loaded on both device form factors, it is necessary to add loading=lazy to the images:

<img id="vertical"   src="https://cdn.glitch.global/6482ece1-578b-4f64-93e8-c1edca8e14d8/bison-vertical.jpg?v=1717451469904" alt="🦬" width="1426" height="1761" loading="lazy">
<img id="horizontal" src="https://cdn.glitch.global/6482ece1-578b-4f64-93e8-c1edca8e14d8/bison-horizontal.jpg?v=1717451240067" alt="🦬" width="2700" height="1761" loading="lazy">

Nevertheless, since these are LCP images for their respective viewport sizes, they also must be loaded with fetchpriority=high which can be achieved with preload links that include media queries:

<link rel="preload" as="image" href="https://cdn.glitch.global/6482ece1-578b-4f64-93e8-c1edca8e14d8/bison-vertical.jpg?v=1717451469904"   fetchpriority="high" media="screen and (orientation: portrait)">
<link rel="preload" as="image" href="https://cdn.glitch.global/6482ece1-578b-4f64-93e8-c1edca8e14d8/bison-horizontal.jpg?v=1717451240067" fetchpriority="high" media="screen and (orientation: landscape)">

Nevertheless, the lcp-lazy-loaded audit is failing for such a page (PSI):

image

This is in spite of the fact that such images load comparably fast to img tags with fetchpriority=high:

Test Case LCP-TTFB
Plain LCP image 139.9 ms
Lazy-loaded LCP image 159.4 ms
Lazy-loaded LCP image with fetchpriority=high attribute 150.4 ms
LCP image with fetchpriority=high attribute 124.5 ms
LCP image with fetchpriority=high preload link 126.5 ms
Lazy-loaded LCP image with fetchpriority=high preload link 129.0 ms

The LCP-TTFB metric comes from the benchmark-web-vitals command in the GoogleChromeLabs/wpp-analysis repo. It reflects the LCP time subtracting the TTFB. The value is the median over 25 requests using the following command:

npm run research -- benchmark-web-vitals -n 25 -f urls.txt -o csv -m LCP-TTFB

For more info, see the Glitch.

What did you expect?

The lcp-lazy-loaded should not have failed.

What have you tried?

I suggest that the following code in the audit be modified:

https://github.com/GoogleChrome/lighthouse/blob/a1d424eef4c8ab1d0ef6c7c71dd7e212316e9ba9/core/audits/lcp-lazy-loaded.js#L88

Given the LCP image's src and srcset it should also check if there is a preload link with fetchpriority=high which has the corresponding href and imagesrcset attribute values. If so, then wasLazyLoaded should be considered false.

How were you running Lighthouse?

PageSpeed Insights, Chrome DevTools

Lighthouse Version

12.0.0

Chrome Version

No response

Node Version

No response

OS

No response

Relevant log output

No response

adamraine commented 5 months ago

Is there any reason you can't use <picture> instead? Even though the image loading doesn't appear to be affected by loading=lazy in this case, I don't see why you need to use it other than to select a specific img src which is a feature that is better provided by <picture> IMO.

westonruter commented 5 months ago

Using a picture element would be an alternative, yes, but it wouldn't always be an option. Not all platforms support generating picture elements, and the different image in question may be located in separate DOM trees. Consider a page which has two separate headers, one for mobile and one for desktop, within each there is a separate img specific to mobile and desktop respectively. A picture element wouldn't work in this case.

My interest here is specifically in the context of developing optimizations on top of WordPress Core. I'm working to take the existing markup generated by the theme and prioritize the loading of the LCP image based on the current viewport. WordPress doesn't support picture yet. My concern is that if the optimization adds loading=lazy to the LCP image while also adds responsive fetchpriority=high preload links, Lighthouse will flag this as an error even though in reality the performance is improved. So a user testing their site in Lighthouse will think the optimization (https://github.com/WordPress/performance/issues/1270) is making things worse.

adamraine commented 5 months ago

I think you could still get picture to work by using two picture elements and loading a "null" data URI image for the viewport size that isn't applicable. That being said this approach is equally hacky if not more hacky compared to the lazy loading approach and you also mentioned how picture may not be available in all cases.

I think we will adopt your suggestion of allowing images with loading=lazy if they have a link preload tag with fetchpriority=high