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 an optional JavaScript fallback.

Jump To The Code

What Is Lazy Image Loading?

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

Similar to other strategies for streamlining the loading process like on-demand, deferred, and asynchronous loading, lazy loading 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 of lazy loading with the potential to drastically reduce initial-load page weight.

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

Before the addition of the loading="lazy" attribute, lazy image loading 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 browser-native lazy image loading is a simpler option.

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 HTML that enables lazy loading directly by the browser without 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.

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 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

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 most users can take advantage of native lazy loading. Others can 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 strong enough that most websites can use the loading="lazy" attribute on its own without all the accoutrement of a JavaScript fallback.

However if your website receives significant traffic from older versions of Safari or other old browsers and includes numerous or large below-the-fold images, the page speed benefit of a simple JavaScript fallback may be worth the added complexity.

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 savings 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