69

Native image lazy-loading for the web

 5 years ago
source link: https://www.tuicool.com/articles/hit/3Mnqy2N
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

In this post, we'll look at the new loading attribute which brings native <img> and <iframe> lazy-loading to the web!. For the curious, here's a sneak preview of it in action:

<img src="celebration.jpg" loading="lazy" alt="..." />
<iframe src="video-player.html" loading="lazy"></iframe>

We are hoping to ship support for loading in Chrome 75 and are working on a deep-dive of the feature we'll publish soon. Until then, let's dive into how loading works.

Introduction

Web pages often contain a large number of images, which contribute to data-usage, page-bloat and how fast a page can load. Many of these images are offscreen, requiring a user to scroll in order to view them.

Historically, to limit the impact offscreen images have on page load times, developers have needed to use a JavaScript library (like LazySizes ) in order to defer fetching these images until a user scrolls near them.

without-lazyload@2x.png A page loading 211 images. The version without lazy-loading fetches 10MB of image data. The lazy-loading version (using LazySizes) loads only 250KB upfront - other images are fetched as the user scrolls through the experience. See WPT .

What if the browser could avoid loading these offscreen images for you? This would help content in the view-port load quicker, reduce overall network data usage and on lower-end devices, reduce memory usage. Well, I'm happy to share that this will soon be possible with the new loading attribute for images and iframes.

The loading attribute

The loading attribute allows a browser to defer loading offscreen images and iframes until users scroll near them. loading supports three values:

  • lazy : is a good candidate for lazy loading.
  • eager : is not a good candidate for lazy loading. Load right away.
  • auto : browser will determine whether or not to lazily load.

Not specifying the attribute at all will have the same impact as setting loading=auto .

loading-attribute@2x.png

Examples

The loading attribute works on <img> (including with srcset and inside <picture> ) as well as on <iframe> :

<!-- Lazy-load an offscreen image when the user scrolls near it -->
<img src="unicorn.jpg" loading="lazy" alt=".."/>

<!-- Load an image right away instead of lazy-loading -->
<img src="unicorn.jpg" loading="eager" alt=".."/>

<!-- Browser decides whether or not to lazy-load the image -->
<img src="unicorn.jpg" loading="auto" alt=".."/>

<!-- Lazy-load images in <picture>. <img> is the one driving image 
loading so <picture> and srcset fall off of that -->
<picture>
  <source media="(min-width: 40em)" srcset="big.jpg 1x, big-hd.jpg 2x">
  <source srcset="small.jpg 1x, small-hd.jpg 2x">
  <img src="fallback.jpg" loading="lazy">
</picture>

<!-- Lazy-load an image that has srcset specified -->
<img src="small.jpg"
     srcset="large.jpg 1024w, medium.jpg 640w, small.jpg 320w"
     sizes="(min-width: 36em) 33.3vw, 100vw"
     alt="A rad wolf" loading="lazy">

<!-- Lazy-load an offscreen iframe when the user scrolls near it -->
<iframe src="video-player.html" loading="lazy"></iframe>

The exact heuristics for "when the user scrolls near" is left up to the browser. In general, our hope is that browsers will start fetching deferred images and iframe content a little before it comes into the viewport. This will increase the change the image or iframe is done loading by the time the user has scrolled to them.

Note: I suggested we name this the loading attribute as it's naming aligns closer with the decoding attribute. Previous proposals, such as the lazyload attribute didn't make it as we needed to support multiple values ( lazy , eager and auto ).

Feature detection

We've kept in mind the importance of being able to fetch and apply a JavaScript library for lazy-loading (for the cross-browser support). Support for loading can be feature-detected as follows:

<script>
if ('loading' in HTMLImageElement.prototype) { 
    // Browser supports `loading`..
} else {
   // Fetch and apply a polyfill/JavaScript library
   // for lazy-loading instead.
}
</script>

Note: You can also use loading as a progressive enhancement. Browsers that support the attribute can get the new lazy-loading behavior with loading=lazy and those that don't will still have images load.

Cross-browser image lazy-loading

If cross-browser support for lazy-loading images is important, it's not enough to just feature-detect and lazy-load a library if you're using <img src=unicorn.jpg loading=lazy /> in the markup.

The markup needs to use something like <img data-src=unicorn.jpg /> (rather than src , srcset or <source> ) to avoid triggering an eager load in browsers that don't support the new attribute. JavaScript can be used to change those attributes to the proper ones if loading is supported and load a library if not.

Below is an example of what this could look like.

  • In-viewport / above-the-fold images are regular <img> tags. A data-src would defeat the preload scanner so we want to avoid it for everything likely to be in the viewport.
  • We use data-src on images to avoid an eager load in unsupported browsers. If loading is supported, we swap data-src for src .
  • If loading is not supported, we load a fallback (LazySizes) and initiate it. Here, we use class=lazyload as a way to indicate to LazySizes images we want to be lazily-loaded.
<!-- Let's load this in-viewport image normally -->
<img src="hero.jpg" alt=".."/>

<!-- Let's lazy-load the rest of these images -->
<img data-src="unicorn.jpg" loading="lazy" alt=".." class="lazyload"/>
<img data-src="cats.jpg" loading="lazy" alt=".." class="lazyload"/>
<img data-src="dogs.jpg" loading="lazy" alt=".." class="lazyload"/>

<script>
(async () => {
    const images = document.querySelectorAll("img.lazyload");
    if ('loading' in HTMLImageElement.prototype) {
        images.forEach(img => {
            img.src = img.dataset.src;
        });
    } else {
        // Dynamically import the LazySizes library
        const lazySizesLib = await import('/lazysizes.min.js');
        // Initiate LazySizes (reads data-src & class=lazyload)
        lazySizes.init(); // lazySizes works off a global.
    }
})();
</script>

Extra notes

  • Try today : Go to chrome://flags and turn on both the "Enable lazy frame loading" and "Enable lazy image loading" flags then restart Chrome. We are updating the implementation from the old attribute name load to loading over the coming weeks.
  • DevTools : An implementation detail of loading in Chrome is that it fetches the first 2KB of images on page-load. It fetches the rest of the image byes when the user is about to see them. This can result in (1) double fetches to 'appear' in the DevTools Network panel and (2) Resource Timing to have 2 requests for each image.
  • Client Hint : Rather than waiting for client-side feature detection to complete, it could be interesting if this check could be done as a HTTP request header. A client hint for conveying loading preferences is being considered but is not yet a 'thing'.

Wrapping up

Give <img loading> a spin and let us know what you think. I'm particularly interested in how folks find the cross-browser story and whether there are any edge-cases we've missed.

References

With thanks to Simon Pieters and Yoav Weiss for their feedback. A large thanks to Ben Greenstein, Scott Little, Raj T and Houssein Djirdeh for their work on LazyLoad.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK