Lazy Load Images

Page Speed Checklist

Native Lazy Image Loading + JavaScript Fallback

Lazy load images to reduce initial-load page weight with the browser-native loading="lazy" attribute and a simple JavaScript fallback.

Jump To The Code

What Is Lazy Image Loading?

Similar to other strategies for streamlining the loading process like on-demand, deferred, and asynchronous loading, lazy loading only loads a resource in anticipation of a need for it.

Fortunately for page speed, images load asynchronously - in the background without render blocking. The downside is that all images on the page are downloaded immediately, whether the user ever sees them or not. Images often account for the greatest share of total page weight, so below-the-fold images are a great application for lazy loading with the potential to drastically reduce initial-load page weight.

Lazy image loading is a technique to avoid unnecessary downloads by delaying below-the-fold images until each image appears on screen.

Live Example

Instructing the browser to load each image just before it's scrolled into view makes the process appear seamless to the user.

JavaScript Lazy Loading

Until recently, lazy Image loading has required JavaScript - typically by reconfiguring <img>s with a placeholder and then detecting when each image comes into view and updating the src with the intended file.

JavaScript methods work well and will continue to have a place as a fallback for older browsers, but there's a simpler option to add to the mix.

iframes

<iframe>s can also be lazy loaded, but in some cases there may be a better option, for example on-demand loading for embedded videos like YouTube.

Native Lazy Images

Browser-native lazy image loading is a newer addition to the HTML spec that enables lazy loading directly by the browser without extra JavaScript.

Along with reducing code overhead, one of the benefits of handling lazy loading in the browser is a greater potential to intelligently adjust loading behavior and scrolling thresholds automatically based on a complex set of variables like internet connection speed and data saver settings.

The loading="lazy" Attribute

Native lazy image loading is as simple as placing the loading="lazy" attribute on each below-the-fold <img> tag:

HTML
<!-- lazy loading below-the-fold image -->
<img loading="lazy" src="beach.jpg" width="800" height="450" alt="beach">

The loading attribute accepts two values:

The eager value may only be needed in the rare case of overriding automatic lazy loading in data-saver modes. (The Chrome browser also accepts an auto value which it treats as default, but recommends against using auto until it's included in the official HTML specification.)

Most modern browsers can take advantage of native lazy loading, but it also degrades gracefully - older browsers will simply ignore the loading="lazy" attribute and load images normally.

HTML Validation

Although it's part of the HTML spec and supported by most browsers, as of 2021 some HTML validators don't yet recognize the loading attribute and will report an error.

loading="lazy" is safe to use with or without a JavaScript fallback as shown below and more validators will presumably add support for native lazy loading as the feature becomes more standardized.

JavaScript Fallback

By combining browser-native lazy image loading with a JavaScript fallback, most users can enjoy faster page loading and an improved user experience.

The fallback to support older browsers involves reconfiguring images with a placeholder, plus a bit of JavaScript to detect when each image comes into view and then replacing it with the final image file.

The examples below use a simple <img> tag, but this technique is adaptable for responsive images with additional attributes like data-srcset. There are also many established libraries, frameworks and plugins that can provide the same result.

The HTML

Along with the loading="lazy" attribute, each <img> tag needs a few changes:

HTML
<img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20WIDTH%20HEIGHT'%3E%3C/svg%3E" data-src="final-image.jpg" width="WIDTH" height="HEIGHT" alt="normal alt text">

<!-- no-JS fallback -->
<noscript><a href="final-image.jpg">view image</a></noscript>

Prevent Content Reflow With Lazy Images

Content reflow happens when elements on a page resize or reposition such that surrounding elements are repositioned as well. Recalculating the layout and repainting a significant portion of the content unnecessarily taxes the browser and can be disorienting to the user.

For pure native lazy image loading, indicating the intrinsic pixel dimensions of the image with the width and height attributes is enough.

The JavaScript fallback needs one more step - a placeholder image with the same proportions as the final image. As in the example above, an inline SVG with customizable dimensions is a simple and lightweight solution to prevent content reflow.

The CSS

CSS is optional, but styling can help indicate the location of images for non-JavaScript users or in the event of an error. For example, a semi-opaque background color for placeholder images:

CSS
/*--- optional styling for placeholder images ---*/
img[data-src] {background-color:rgba(0,0,0,.1)}

The JavaScript

A little vanilla JavaScript detects support for loading="lazy" or IntersectionObserver (or neither) and updates the HTML accordingly:

JS
// <img>s with data-src attribute
var lazyimages = document.querySelectorAll('img[data-src]');

// IntersectionObserver IS supported AND native lazy loading is NOT (fallback for loading="lazy")
if ('IntersectionObserver' in window && !('loading' in HTMLImageElement.prototype)) {
    var imageObserver = new IntersectionObserver(function(entries, observer) {
        entries.forEach(function(entry) {
            if (entry.isIntersecting) { // image enters viewport
                var image = entry.target;
                image.src = image.dataset.src; // replace src value with data-src value
                image.removeAttribute('data-src'); // remove data-src attribute 
                imageObserver.unobserve(image); // stop observing image
            }
        });
    }, {rootMargin:'500px 0px'}); // 500px buffer to load images just before they're visible

    lazyimages.forEach(function(image) { // run for each image
        imageObserver.observe(image);
    });
}

// native lazy loading IS supported OR IntersectionObserver is NOT (fallback not used)
else {
    for (var i = 0; i < lazyimages.length; i++) { // run for each image
        lazyimages[i].src = lazyimages[i].dataset.src; // replace src value with data-src value
        lazyimages[i].removeAttribute('data-src'); // remove data-src attribute 
    }
}

(In the interest of simplicity, this snippet doesn't include the EventListener method that was common prior to IntersectionObserver, which can be added as a secondary fallback for slightly deeper browser support.)

As always, be sure to minify the code to reduce file size and logically consolidate with other files for the most efficient file compression.

Browser Support For Native Lazy Images

The browser share for every website is different, but as of 2021 most users can take advantage of native lazy loading. Others will use the JavaScript fallback and a few much older browsers will load images normally.

According to CanIUse.com on the loading attribute and fallbacks:

Tailored For Your Users

Although relatively new, browser support for native lazy loading is already strong - strong enough that many websites can potentially use the loading="lazy" attribute on its own without all the accoutrement of a JavaScript fallback.

For example, about 95% of the visitors to PageSpeedChecklist.com can benefit from native lazy loading. There are also relatively few below-the-fold images so the potential impact is minimal.

On the other hand, if your website receives significant traffic from Safari or older browsers or includes numerous large below-the-fold images, the page speed benefit of keeping a simple JavaScript fallback is well worth the minimal added complexity and will be important to keep in place for some time to come.

Live Example

These example images take advantage of native lazy loading for browsers that support it with a JavaScript fallback for those that don't.

Lazy loading these images saves about 240KB of initial-load page weight, but for pages with many large below-the-fold content images the saving can be much greater.

Even More Speed

Lazy loading is just one of many powerful strategies to streamline the loading process. Take advantage of every opportunity to maximize speed with the complete page speed checklist:

Page Speed Checklist