Defer image loading with pure JavaScript

This tutorial will show you how to defer image loading until the entire document (DOM) has been loaded and until images are intersecting the viewport. We are going to lazy load images with pure JavaScript and provide different image sizes for different screen sizes.

You may want to defer image loading to improve page speed and to save bandwidth on mobile devices. Lazy loading of images means that images only is loaded if they are in the viewport and that they are loaded after the entire document (DOM) has been loaded.

This code has been tested and is working with Google Chrome (75.0.3770.100), Mozilla Firefox (67.0.4) and Microsoft Edge (42.17134.1.0), without any polyfill. It works in Internet Explorer (11.829.17134.0) with a polyfill for IntersectionObserver. If you want to support older browsers, check out our post on transpilation and polyfilling of JavaScript.

JavaScript

We have added an event listener to defer image loading until the document (DOM) has been loaded and we use an intersection observer to only load images when they intersect the viewport.

// Initialize when DOM content has been loaded
document.addEventListener('DOMContentLoaded', start, false);

// Start this instance
function start()
{
    // Lazy load images
    lazyLoadImages();

} // End of the start method

// Lazy load images
function lazyLoadImages()
{
    // Get images
    var images = document.querySelectorAll('img.lazy');

    // Create a new IntersectionObserver (Polyfilled)
    var observer = new IntersectionObserver(function (entries) {

        // Loop entries
        for (i = 0; i < entries.length; i++) {

            if (entries[i].isIntersecting === true) {
                var image = entries[i].target;
                var src = image.getAttribute('data-src');
                var srcset = image.getAttribute('data-srcset');
                if (src !== null) { image.src = src; }
                if (srcset !== null) { image.srcset = srcset; }
                image.classList.remove('lazy');
                observer.unobserve(image);
            }
        }
    });

    // Loop images
    for (i = 0; i < images.length; i++) {
        observer.observe(images[i]);
    }

} // End of the lazyLoadImages method

Example with html

We can use one image source in an img tag or a set of multiple image sizes, the browser will select the image to use in a source set. We add lazy as a class to images that should have deferred loading. Older browser does not support the srcset attribute and we therefore need to add a data-src attribute as a fallback. You will also need a polyfill for IntersectionObserver if you need to support older browsers.

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Lazy image loading</title>
    <style>
        .annytab-image{
            max-width: 100%;
        }
        .lazy{
            visibility: hidden;
        }
    </style>
</head>
<body style="width:100%;font-family:Arial, Helvetica, sans-serif;padding:0;margin:0;">

    <!-- One size image fits all screens -->
    <img alt="One image size fits all screens!" class="lazy annytab-image" data-src="/images/fall_nature.jpg" />

    <!-- Multiple image sizes for different screens, data-src is fallback for IE 11 and older browsers -->
    <img alt="Multiple image sizes for different screens!"
         class="lazy annytab-image"
         data-src="/images/adventure-clouds-640x360.jpg"
         data-srcset="/images/adventure-clouds-640x360.jpg 640w,
                     /images/adventure-clouds-1280x720.jpg 1280w,
                     /images/adventure-clouds-1920x1080.jpg 1920w,
                     /images/adventure-clouds-3840x2160.jpg 3840w" />

    <!-- Scripts -->
    <!--<script src="/js/polyfills/intersection.observer.min.js"></script>-->
    <script>

        // Initialize when DOM content has been loaded
        document.addEventListener('DOMContentLoaded', start, false);

        // Start this instance
        function start()
        {
            // Lazy load images
            lazyLoadImages();

        } // End of the start method

        // Lazy load images
        function lazyLoadImages()
        {
            // Get images
            var images = document.querySelectorAll('img.lazy');

            // Create a new IntersectionObserver (Polyfilled)
            var observer = new IntersectionObserver(function (entries) {

                // Loop entries
                for (i = 0; i < entries.length; i++) {

                    if (entries[i].isIntersecting === true) {
                        var image = entries[i].target;
                        var src = image.getAttribute('data-src');
                        var srcset = image.getAttribute('data-srcset');
                        if (src !== null) { image.src = src; }
                        if (srcset !== null) { image.srcset = srcset; }
                        image.classList.remove('lazy');
                        observer.unobserve(image);
                    }
                }
            });

            // Loop images
            for (i = 0; i < images.length; i++) {
                observer.observe(images[i]);
            }

        } // End of the lazyLoadImages method

    </script>

</body>
</html>

Leave a Reply

Your email address will not be published. Required fields are marked *