Clean Markup

Lazy Loading Images with the Intersection Observer API

Lazy Loading Images with the Intersection Observer API

Images often account for more than 60% of a web page's total byte weight. Historically, lazy-loading offscreen images required listening to the scroll and resize events and calling getBoundingClientRect() on every image—forcing synchronous layout recalculations on the main thread.

The native IntersectionObserver API solves this cleanly by letting the browser notify your callback asynchronously whenever a target element enters (or approaches) the viewport.

Example

<!-- Store the high-res URL in data-src until the image approaches the viewport -->
<img
  class="lazy-image"
  src="/img/placeholder.svg"
  data-src="/img/posts/article-cover.webp"
  alt="Article preview"
/>

Input : Offscreen <img class="lazy-image" data-src="..."> elements
Output : Asynchronous, zero-jank image loading triggered 200px before entering the viewport.

Implementation

function initLazyImages() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy-image"));

  // Fallback for older browsers without IntersectionObserver support
  if (!("IntersectionObserver" in window)) {
    lazyImages.forEach(function (img) {
      img.src = img.getAttribute("data-src");
      img.classList.remove("lazy-image");
    });
    return;
  }

  var imageObserver = new IntersectionObserver(
    function (entries, observer) {
      entries.forEach(function (entry) {
        if (entry.isIntersecting) {
          var img = entry.target;
          img.src = img.getAttribute("data-src");
          img.classList.remove("lazy-image");
          observer.unobserve(img);
        }
      });
    },
    {
      rootMargin: "200px 0px",
      threshold: 0.01,
    }
  );

  lazyImages.forEach(function (img) {
    imageObserver.observe(img);
  });
}

document.addEventListener("DOMContentLoaded", initLazyImages);

Main Point: Setting rootMargin: "200px 0px" starts fetching the image 200 pixels before the user scrolls to it, ensuring the image is already decoded by the time it enters the visible viewport.

Credits

Photo by Ilya Pavlov

← Home