W3docs

Lazy load images with JavaScript

Read this tutorial and learn detailed information about some simple and fast techniques that are used to decrease image loading time using 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 for 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 and resize events which are equally important:

Lazy loading example with JS

<!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() {
            lazyloadImages.forEach(function(img) {
              if(img.getBoundingClientRect().top < window.innerHeight) {
                img.src = img.dataset.src;
                img.classList.remove('lazy-load');
              }
            });
            if(lazyloadImages.length == 0) {
              document.removeEventListener("scroll", lazyload);
              window.removeEventListener("resize", lazyload);
            }
          }, 20);
        }
        document.addEventListener("scroll", lazyload);
        window.addEventListener("resize", 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. These 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 by checking the image's position relative to the viewport using getBoundingClientRect(). 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:

Intersection Observer API code example

<!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() {
              lazyloadImages.forEach(function(img) {
                if(img.getBoundingClientRect().top < window.innerHeight) {
                  img.src = img.dataset.src;
                  img.classList.remove('lazy-load');
                }
              });
              if(lazyloadImages.length == 0) {
                document.removeEventListener("scroll", lazyload);
                window.removeEventListener("resize", lazyload);
              }
            }, 20);
          }
          document.addEventListener("scroll", lazyload);
          window.addEventListener("resize", lazyload);
        }
      })
    </script>
  </body>
</html>
Info

The Intersection Observer API is widely supported in all modern browsers. The fallback code ensures compatibility with older 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.

Note: Modern browsers also support native lazy loading via the loading='lazy' HTML attribute, which requires no JavaScript and is recommended for simple use cases.