Lazy Loading Images and Content with the Intersection Observer API

Published on: by Dr. Talib

Website performance is critical for user experience and SEO. One of the biggest drags on initial page load time is having to download every single image on a long page, even the ones far below the fold. **Lazy loading** is the technique of deferring the loading of off-screen resources until they are actually needed. The modern, efficient way to achieve this is with the **Intersection Observer API**.


What is the Intersection Observer?

The Concept: Before this API, developers had to use complex and inefficient JavaScript that constantly checked an element's position relative to the viewport during scroll events. The Intersection Observer API is a browser feature that does this work for us, but in a highly optimized way.

It allows you to create an "observer" that will execute a callback function whenever a target element you are watching either enters or leaves the viewport (or another specified element).

It efficiently answers the question: "Is this element currently visible on the screen?"

A Practical Example: Lazy Loading Images

The most common use case is for images. The strategy is simple:

  1. In your HTML, don't put the real image URL in the src attribute. Instead, put it in a data attribute, like data-src. You can put a tiny, low-quality placeholder in the actual `src`.
  2. Use JavaScript to "observe" all the images you want to lazy load.
  3. When the Intersection Observer tells you an image has entered the viewport, you take the URL from data-src and place it into the src attribute. The browser will then download the full-quality image.

The HTML and JavaScript Implementation:

<style>
  .spacer { height: 100vh; }
  img.lazy { 
    opacity: 0;
    transition: opacity 0.5s;
    min-height: 300px; /* To reserve space */
    background: #eee;
  }
  img.lazy.loaded { opacity: 1; }
</style>

<div class="spacer">Scroll Down...</div>

<!-- Notice the 'src' is a placeholder, and the real URL is in 'data-src' -->
<img class="lazy" src="placeholder-1x1.gif" data-src="https://via.placeholder.com/600x300/4f46e5/ffffff?text=Image+1" alt="Lazy Loaded Image 1">

<script>
  document.addEventListener("DOMContentLoaded", () => {
    const lazyImages = document.querySelectorAll('img.lazy');

    const imageObserver = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        // isIntersecting is true when the element enters the viewport
        if (entry.isIntersecting) {
          const image = entry.target;
          image.src = image.dataset.src; // Swap src with data-src
          image.classList.add('loaded');
          observer.unobserve(image); // Stop observing this image once loaded
        }
      });
    });

    lazyImages.forEach(image => {
      imageObserver.observe(image);
    });
  });
</script>

Try it Yourself: Paste this code into the HTML Viewer. When the page loads, the image won't be there. As you scroll down and the image area enters the preview pane, the JavaScript will trigger, load the real image, and fade it in. Check your browser's Network tab in DevTools to confirm the image only downloads when it becomes visible.

Configuring the Observer

The `IntersectionObserver` constructor can take a second argument, an `options` object, to customize its behavior:

  • root: The element that is used as the viewport for checking visibility. It defaults to the browser viewport if not specified.
  • rootMargin: An offset, similar to CSS margin, that lets you trigger the observer when the element is, for example, 200px *before* it enters the viewport. This makes images load just before the user sees them for a smoother experience. Example: '0px 0px -200px 0px'.
  • threshold: A number between 0.0 and 1.0 indicating at what percentage of the target's visibility the observer's callback should be executed. A value of 1.0 means the callback will only fire when 100% of the element is visible.
const options = {
  rootMargin: '0px 0px -50px 0px',
  threshold: 0.5 // Fire when 50% of the element is visible
};
const myObserver = new IntersectionObserver(callback, options);

More Than Just Images

While images are the primary use case, you can use the Intersection Observer for anything. You can lazy load entire sections of a page, trigger animations only when they scroll into view, or pause/play videos automatically. It's a highly performant and versatile API that has become an essential tool for building modern, fast-loading websites.