Lazy load images with JavaScript

Loading an image on the website can require a long time. It's a big problem affecting the user experience because the visitors have to wait before accessing the content. We will walk you through some techniques to lazy loading images that can improve user experience and increase the number of your web visitors.

In this tutorial, we will show you some techniques of lazy load images using JavaScript.

JavaScript Events

We need scroll, resize and orientationChange events which are equally important:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
    <style>
      img {
        width: 500px;
        height: 350px;
        display: block;
        margin: 10px auto;
      }
    </style>
  </head>
  <body>
    <img src="https://images.unsplash.com/photo-1482784160316-6eb046863ece?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img src="https://images.unsplash.com/photo-1488441770602-aed21fc49bd5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjExMDk0fQ&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1545105511-839f4a45a030?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1493246507139-91e8fad9978e?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1473800447596-01729482b8eb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1491250974528-9443b2902f18?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1473800447596-01729482b8eb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1500993855538-c6a99f437aa7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1501018313157-78d10f597133?ixlib=rb-1.2.1&auto=format&fit=crop&w=1055&q=80" />
    <script>
      document.addEventListener("DOMContentLoaded", function() {
        let lazyloadImages = document.querySelectorAll("img.lazy-load");
        let lazyloadThrottleTimeout;

        function lazyload() {
          if(lazyloadThrottleTimeout) {
            clearTimeout(lazyloadThrottleTimeout);
          }
          lazyloadThrottleTimeout = setTimeout(function() {
            let scrollTop = window.pageYOffset;
            lazyloadImages.forEach(function(img) {
              if(img.offsetTop < (window.innerHeight + scrollTop)) {
                img.src = img.dataset.src;
                img.classList.remove('lazy');
              }
            });
            if(lazyloadImages.length == 0) {
              document.removeEventListener("scroll", lazyload);
              window.removeEventListener("resize", lazyload);
              window.removeEventListener("orientationChange", lazyload);
            }
          }, 20);
        }
        document.addEventListener("scroll", lazyload);
        window.addEventListener("resize", lazyload);
        window.addEventListener("orientationChange", lazyload);
      });
    </script>
  </body>
</html>

The scroll event reports when the document view or an element has been scrolled. The resize event reports when the document view (window) has been resized and the orientationChange event reports when the orientation of the device has changed. These three events catch the changes in the screen and specify the number of visible images and trigger them to load.

If any of these events are present, we will find all the deferred pictures on the page and check which ones are in the viewport at the moment. It's performed with the top offset of an image, the top position of the document, and the height of the window. If the image has entered the viewport, we pick the URL from the data-src attribute and move it to the src attribute to load the image.

Intersection Observer API

You can achieve the same result using the Intersection Observer API:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
    <style>
      img {
        width: 500px;
        height: 350px;
        display: block;
        margin: 10px auto;
      }
    </style>
  </head>
  <body>
    <img src="https://images.unsplash.com/photo-1482784160316-6eb046863ece?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img src="https://images.unsplash.com/photo-1488441770602-aed21fc49bd5?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjExMDk0fQ&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1545105511-839f4a45a030?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1493246507139-91e8fad9978e?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1473800447596-01729482b8eb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1491250974528-9443b2902f18?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1473800447596-01729482b8eb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1500993855538-c6a99f437aa7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" />
    <img class="lazy-load" data-src="https://images.unsplash.com/photo-1501018313157-78d10f597133?ixlib=rb-1.2.1&auto=format&fit=crop&w=1055&q=80" />
    <script>
      document.addEventListener("DOMContentLoaded", function() {
        let lazyloadImages;
        if("IntersectionObserver" in window) {
          lazyloadImages = document.querySelectorAll(".lazy-load");
          let imageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
              if(entry.isIntersecting) {
                let image = entry.target;
                image.src = image.dataset.src;
                image.classList.remove("lazy-load");
                imageObserver.unobserve(image);
              }
            });
          });
          lazyloadImages.forEach(function(image) {
            imageObserver.observe(image);
          });
        } else {
          let lazyloadThrottleTimeout;
          lazyloadImages = document.querySelectorAll(".lazy-load");

          function lazyload() {
            if(lazyloadThrottleTimeout) {
              clearTimeout(lazyloadThrottleTimeout);
            }
            lazyloadThrottleTimeout = setTimeout(function() {
              let scrollTop = window.pageYOffset;
              lazyloadImages.forEach(function(img) {
                if(img.offsetTop < (window.innerHeight + scrollTop)) {
                  img.src = img.dataset.src;
                  img.classList.remove('lazy-load');
                }
              });
              if(lazyloadImages.length == 0) {
                document.removeEventListener("scroll", lazyload);
                window.removeEventListener("resize", lazyload);
                window.removeEventListener("orientationChange", lazyload);
              }
            }, 20);
          }
          document.addEventListener("scroll", lazyload);
          window.addEventListener("resize", lazyload);
          window.addEventListener("orientationChange", lazyload);
        }
      })
    </script>
  </body>
</html>
Lazy load image using the Intersection Observer API is not supported on all browsers.

The Intersection Observer API presents a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document viewport. It allows avoiding math and gets the same result as in the previous method.

The isIntersecting property is used to pick the URL from the data-src attribute and move it to the src attribute to trigger the image load. After doing this, we remove the "lazy-load" class and observer from that image.