Hybrid Lazy Loading: A Progressive Migration To Native Lazy Loading

About The Author

Andrea has been programming computers for 30 years and he’s now a front-end development addicted who loves to craft websites with the user experience at the … More about Andrea ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

Native lazy loading is coming to the web. Since it doesn’t depend on JavaScript, it will revolutionize the way we lazy load content today, making it easier for developers to lazy load images and iframes. But it’s not a feature we can polyfill, and it will take some time before it becomes usable across all browsers. In this article, you’ll learn how it works and how you can progressively replace your JavaScript-driven lazy loading with its native alternative, thanks to hybrid lazy loading.

In the past few weeks, you might have heard or read about native lazy loading, which is coming to Chromium 75 in the upcoming months.

“Yeah, great news, but we’ll have to wait until all browsers support it.”

If this was the first thing that crossed your mind, keep reading. I will try and convince you of the opposite.

Let’s start with a comparison between native lazy loading and the good ol’ JavaScript-driven one.

Native Versus JavaScript-Driven Lazy Loading

Lazy loading is a way to improve the performance of a website or web application by maximizing the rendering speed of the above-the-fold images and iframes (and sometimes videos) by deferring the loading of below-the-fold content.

JavaScript-Driven Lazy Loading

In order to lazy load images or iframes, it’s a very common practice to mark them up by replacing the proper src attribute with a similar data attribute, data-src, then to rely on a JavaScript solution to detect when the images/iframes are getting close to the visible portion of the website (typically because the user scrolled down) and to copy the data attributes into the proper ones, then triggering the deferred loading of their content.

<img data-src="turtle.jpg" alt="Lazy turtle" class="lazy">

Native Lazy Loading

According to the native lazy loading specification (still under development), if you want to lazy load images or iframes using the native lazy loading feature, you would just need to add the loading=lazy attribute on the related tag.

<img src="turtle.jpg" alt="Lazy turtle" loading="lazy">

Addy Osmani wrote extensively about this topic in his article “Native Image Lazy-Loading For The Web!” in which he stated that the Google Chrome team are already developing the feature and intend to ship it in Chrome 75.

Other Chromium-based browsers like Opera and Microsoft Edge will also benefit from this development by gaining the same feature in their first update based on Chromium 75.

Get Started With Native Lazy Loading

In case your website’s images are downloaded all at once at page landing without lazy loading, you can enable (where supported) native lazy loading in your website as easily as adding an HTML attribute. The loading attribute tells browsers which images are important to load immediately, and which ones can be downloaded lazily as the users scroll down. The same attribute can be applied to iframes.

In order to tell browsers that a particular image is important so they can load it as soon as possible, you must add the loading="eager" attribute on the img tag. The best practice is to do this for the primary images — typically for the ones that will be displayed above the fold.

<img src="rabbit.jpg" alt="Fast rabbit" loading="eager">

To tell browsers that an image should be downloaded lazily, just add the loading="lazy" attribute. This is a best practice only if you do it only to secondary images — typically for the ones will be displayed below the fold.

<img src="turtle.jpg" alt="Lazy turtle" loading="lazy">

Just by adding the loading attribute to your images and iframes, you will enable your website to use native lazy loading as a progressive enhancement. Your website will gradually benefit from it as support arrives to your users in most modern browsers.

This is the best approach to use if your website is not using any kind of lazy loading today, but if you already implemented a JavaScript-driven lazy loading solution, you might want to keep it while progressively switching to native lazy loading.

The ideal solution would be to start using native lazy loading right away, and use a polyfill to make it work across all browsers. Unfortunately, native lazy loading is not a feature we can polyfill with JavaScript.

No Use For A Polyfill

When a new browser technology is released to a single browser, the open-source community usually releases a JavaScript polyfill to provide the same technology to the rest of the browsers. For example, the IntersectionObserver polyfill uses JavaScript and DOM elements to coordinate Element.getBoundingClientRect() to reproduce the behavior of the native API.

But the case of native lazy loading is different because a JavaScript polyfill for loading="lazy" would have to prevent browsers from loading content as soon as they find a URL in the markup of an image or iframe. JavaScript has no control on this initial stage of page rendering, therefore it is not possible to polyfill native lazy loading.

Hybrid Lazy Loading

If you are not satisfied with having native lazy loading only as a progressive enhancement, or you have already implemented JavaScript-based lazy loading and don’t want to lose this feature in less modern browsers (but still want to enable native lazy loading on browsers that support it), then you need a different solution. Introducing: hybrid lazy loading.

Hybrid lazy loading is a technique to use native lazy loading on browsers that support it, otherwise, rely on JavaScript to handle the lazy loading.

In order to do hybrid lazy loading, you need to mark up your lazy content using the data attributes instead of the real ones (such as in JavaScript-driven lazy loading), and to add the loading="lazy" attribute.

<img data-src="turtle.jpg" loading="lazy" alt="Lazy turtle">

Then you require some JavaScript. In the first place, you need to detect whether or not native lazy loading is supported by the browser. Then, do one of the following for every element with the loading="lazy" attribute:

  • If native lazy loading is supported, copy the data-src attribute value to the src attribute;
  • If not supported, initialize a JavaScript lazy loading script or plugin to do that as the elements enter the viewport.

It’s not very hard to write the JavaScript code required to perform these operations on your own. You can detect if native lazy loading is supported with the condition:

if ('loading' in HTMLImageElement.prototype)

If it is, just copy the src attribute value from data-src. If it’s not, initialize some lazy-loading script of your choice.

Here’s a snippet of code that does that.

<!-- In-viewport images should be loaded normally, or eagerly -->
<img src="important.jpg" loading="eager" alt="Important image">

<!-- Let’s lazy-load the rest of these images -->
<img data-src="lazy1.jpg" loading="lazy" alt="Lazy image 1">
<img data-src="lazy2.jpg" loading="lazy" alt="Lazy image 2">
<img data-src="lazy3.jpg" loading="lazy" alt="Lazy image 3">

<script>
  (function() {
    if ("loading" in HTMLImageElement.prototype) {
      var lazyEls = document.querySelectorAll("[loading=lazy]");
      lazyEls.forEach(function(lazyEl) {
        lazyEl.setAttribute(
          "src",
          lazyEl.getAttribute("data-src")
        );
      });
    } else {
      // Dynamically include a lazy loading library of your choice
      // Here including vanilla-lazyload
      var script = document.createElement("script");
      script.async = true;
      script.src =
        "https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.0.0/dist/lazyload.min.js";
      window.lazyLoadOptions = {
        elements_selector: "[loading=lazy]"
        //eventually more options here
      };
      document.body.appendChild(script);
    }
  })();
</script>

You can find and test the code above in this live demo.

Still, that is a very basic script, and things can get complicated when you’re using additional attributes or tags to get responsive images (such as the srcset and sizes attributes or even the picture and source tags).

A Little Third-Party Help

For the past four years, I’ve been maintaining an open-source lazy load script named “vanilla-lazyload” and, in a couple of days after Addy Osmani wrote about native lazy loading, the community reacted by asking me if my script could act as a polyfill.

As I explained before, you cannot create a polyfill for the native lazy loading feature, however, I thought of a solution that would make it easier for developers to begin the transition to native lazy loading, without needing to write any of the JavaScript code that I’ve mentioned before.

Starting from version 12 of vanilla-lazyload, you can just set the use_native option to true to enable hybrid lazy loading. The script is only 2.0 kB gzipped and it’s already available on GitHub, npm, and jsDelivr.

Demos

You can start playing around with native lazy loading today by downloading Chrome Canary or Microsoft Edge Insider (dev channel) then enabling the flags “Enable lazy image loading” and “Enable lazy frame loading”. To enable these flags, enter about:flags in your browser’s URL field and search for “lazy” in the search box.

Native Lazy Loading Demo

To analyze how native lazy loading works in the developer tools, you can start playing with the following demo. In this one, not a single line of JavaScript is used. Yes, it’s just full plain native lazy loading.

What to expect: All images are fetched at once, but with different HTTP responses. The ones with the response code 200 are the eagerly loaded images, while the ones with the response code 206 are only partially fetched in order to get the initial information about the images. Those images will then be fetched completely with a 200 response code when you scroll down.

Hybrid Lazy Loading Demo

To analyze how hybrid lazy loading works, you can start playing with the next demo. Here, vanilla-lazyload@12.0.0 is used and the use_native option is set to true:

What to expect: Try the demo on different browsers to see how it behaves. On browsers that support native lazy loading, the behavior would be the same as in the native lazy loading demo. On browsers that do not support native lazy loading, the images will be downloaded as you scroll down.

Please note that vanilla-lazyload uses the IntersectionObserver API under the hood, so you would need to polyfill it on Internet Explorer and less recent versions of Safari. It’s not a big deal if a polyfill is not provided, though, because in that case vanilla-lazyload would just download all the images at once.

Note: Read more in the “To Polyfill Or Not To Polyfill” chapter of vanilla-lazyload’s readme file.

Try Hybrid Lazy Loading In Your Website

Since native lazy loading is coming soon to some browsers, why don’t you give it a chance today using hybrid lazy loading? Here’s what you need to do:

HTML Markup

The simplest image markup is made by two attributes: src and alt.

For the above-the-fold images, you should leave the src attribute and add the loading="eager" attribute.

<img src="important.jpg" loading="eager" alt="Important image">

For below-the-fold images, you should replace the src attribute with the data attribute data-src and add the loading="lazy" attribute.

<img data-src="lazy.jpg" loading="lazy" alt="A lazy image">

If you want to use responsive images, do the same with the srcset and sizes attributes.

<img alt="A lazy image" 
    loading="lazy" 
    data-src="lazy.jpg" 
    data-srcset="lazy_400.jpg 400w, lazy_800.jpg 800w" 
    data-sizes="100w">

If you prefer to use the picture tag, change the srcset, sizes and src also in the source tags.

<picture>
    <source 
        media="(min-width: 1200px)" 
        data-srcset="lazy_1200.jpg 1x, lazy_2400.jpg 2x">
    <source 
        media="(min-width: 800px)" 
        data-srcset="lazy_800.jpg 1x, lazy_1600.jpg 2x">
    <img alt="A lazy image" 
        loading="lazy" 
        data-src="lazy.jpg">
</picture>

The picture tag can also be used to selectively load the WebP format for your images.

Note: If you want to know more usages of vanilla-lazyload, please read the “Getting Started” HTML section of its readme file.

JavaScript Code

First of all, you need to include vanilla-lazyload on your website.

You can load it from a CDN like jsDelivr:

<script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.0.0/dist/lazyload.min.js"></script>

Or you can install it using npm:

npm install vanilla-lazyload@12

It’s also possible to use an async script with automatic initialization; load it as a ES module using type="module" or load it as AMD using RequireJS. Find more ways to include and use vanilla-lazyload in the “Getting Started” script section of the readme file.

Then, in your website/web application’s JavaScript code, include the following:

var pageLazyLoad = new LazyLoad({
    elements_selector: "[loading=lazy]",
    use_native: true // ← enables hybrid lazy loading
});

Note: The script has plenty of other settings you can use to customize vanilla-lazyload’s behavior, e.g. to increase the distance of the scrolling area from which to start loading the elements or to load elements only if they stayed in the viewport for a given time. Find more settings in the API section of the readme file.

All Together, Using An async Script

To put it all together and use an async script to maximize performance, please refer to the following HTML and JavaScript code:

<!-- In-viewport images should be loaded normally, or eagerly -->
<img src="important.jpg" loading="eager" alt="Important image">

<!-- Let’s lazy-load the rest of these images -->
<img data-src="lazy1.jpg" loading="lazy" alt="Lazy image 1">
<img data-src="lazy2.jpg" loading="lazy" alt="Lazy image 2">
<img data-src="lazy3.jpg" loading="lazy" alt="Lazy image 3">

<!-- Set the options for the global instance of vanilla-lazyload -->
<script>
  window.lazyLoadOptions = {
    elements_selector: "[loading=lazy]",
    use_native: true // ← enables hybrid lazy loading
  };
</script>

<!-- Include vanilla lazyload 12 through an async script -->
<script async src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.0.0/dist/lazyload.min.js"></script>

That’s it! With these very simple and easy steps, you’ll have enabled hybrid lazy loading in your website!

Important Best Practices

  • Apply lazy loading only to the images that you know that will probably be displayed below the fold. Eagerly load the ones above the fold to maximize performance. If you just apply lazy load to all images in your page, you’ll slow down rendering performance.
  • Use CSS to reserve some space for the images before they are loaded. That way, they’ll push the rest of the content below. If you don’t do that, a larger number of images will be placed above the fold before they should, triggering immediate downloads for them. If you need a CSS trick to do that, you can find one in the tips and tricks section of the readme of vanilla-lazyload.

Pros And Cons

NATIVE LAZY LOADING
PROS
  • No JavaScript required;
  • No setup headaches, it just works;
  • No need to reserve space for images using CSS tricks;
CONS
  • It does not work today on all browsers;
  • The initial payload is higher, because of the prefetch of the initial 2 kb for every image.
JAVASCRIPT-DRIVEN LAZY LOADING
PROS
  • It works consistently across all browsers, right now;
  • You can do very highly customized UI tricks, like the blur-in effect or the delayed loading.
CONS
  • It relies on JavaScript to load your content.
HYBRID LAZY LOADING
PROS
  • It gives you the chance to enable and test native lazy loading where supported;
  • It enables lazy loading on all browsers;
  • You can transparently remove the script dependency as soon as native lazy loading support will be widespread.
CONS
  • It still relies on JavaScript to load your content.

Wrapping Up

I’m very excited that native lazy loading is coming to browsers, and I can’t wait for all browser vendors to implement it!

In the meantime, you can either choose to enrich your HTML markup for progressive enhancement and get native lazy loading only where supported, or you can go for hybrid lazy loading and get both native and JavaScript-driven lazy loading until the day native lazy loading will be supported by the vast majority of browsers.

Give it a try! Don’t forget to star/watch vanilla-lazyload on GitHub, and let me know your thoughts in the comments section.

Further Reading

Smashing Editorial (dm, il, mrn)