Lazy Loading Images

Page Speed Checklist

Native Lazy Image Loading With JavaScript Fallback

Simplify lazy image loading with the browser-native loading="lazy" attribute with an IntersectionObserver JavaScript fallback. Very old browsers that don't support either will load images normally.

Live Example


Indicating native image dimensions with the width & height attributes and an inline placeholder image prevent content reflow while the data-src attribute stores the path of the real/final image:

<img src="data:image/svg+xml,%3Csvg%20xmlns=''%20viewBox='0%200%20IMAGEWIDTH%20IMAGEHEIGHT'%3E%3C/svg%3E" data-src="REAL IMAGE PATH" width="IMAGE WIDTH" height="IMAGE HEIGHT" alt="existing alt text" loading="lazy">

<!-- no-JS fallback -->
<noscript><a href="REAL IMAGE PATH">view image</a></noscript>

(If the <img> is inside a link, place the <noscript> fallback outside the <a>.)


Image placeholders can be styled as desired:

/*--- 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:


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

// IntersectionObserver IS supported AND native lazy loading is NOT (newer but not newest browsers)
if ('IntersectionObserver' in window && !('loading' in HTMLImageElement.prototype)) {
    // lazy load images
    var imageObserver = new IntersectionObserver(function(entries, observer) {
        entries.forEach(function(entry) {
            if (entry.isIntersecting) {
                var image =;
                image.src = image.dataset.src;
    }, {rootMargin:'500px 0px'}); // 500px buffer

    lazyimages.forEach(function(image) {

// native lazy loading IS supported OR IntersectionObserver is NOT (very new or very old browsers)
else {
    // replace src value with data-src value
    for (var i = 0; i < lazyimages.length; i++) {
        lazyimages[i].src = lazyimages[i].dataset.src;

And minified:

var lazyimages=​document​.querySelectorAll​("img[data-src]"); if("IntersectionObserver"in window && !("loading"in HTMLImageElement​.prototype)){ var imageObserver=​new IntersectionObserver​(function (entries,observer) { entries​.forEach(function(entry){ if(entry​.isIntersecting)​{var; image.src = image​.dataset.src; image​.removeAttribute("data-src"); imageObserver​.unobserve(image)}})},​{rootMargin:"500px 0px"}); lazyimages​.forEach​(function(image)​{imageObserver​.observe​(image)})} else{for(var i=0; i< lazyimages​.length; i++)​{lazyimages[i]​.src=lazyimages[i]​.dataset.src; lazyimages[i]​.removeAttribute​("data-src")}}
Browser Support

The audience of any particular website will vary, but as of 2020 many if not most users can take advantage of native lazy loading while some will use the JavaScript fallback and a few will load images right away.

  • ~70% support loading="lazy"
    Chrome, Firefox, Edge (Blink), Opera

  • ~21% support IntersectionObserver (but not loading="lazy")
    Safari, Edge (pre-Blink)

  • ~9% support neither
    IE, older Safari & other old browsers

(In the interest of simplicity and forward-leaning development, this setup does away with the EventListener JavaScript method that was common prior to IntersectionObserver and is sometimes still used as a fallback.)

iframe Options

This setup can also support <iframe>s by adding an iframe[data-src] selector, however many uses of <iframe>s may have a better alternative, for example on-demand loading for embedded videos like YouTube.

Live Example

Lazy loading these images saves up to 240KB of initial-load page weight:

Even More Speed

Lazy loading is just one of several important strategies to reduce initial-load page weight and streamline the loading process. Complete the full page speed checklist to take advantage of every opportunity to maximize speed and in turn, user experience.

Page Speed Checklist