Back to APIs

IntersectionObserver

Observers

An API that provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document viewport.

SupportGlobal 97%+
BaselineWidely available
ImpactHigh
Popularity🔥 85

When to Use

  • Lazy loading images or heavy components.
  • Implementing infinite scrolling interfaces.
  • Reporting visibility of advertisements.
  • Triggering animations when elements scroll into view.

When NOT to Use

  • Tracking precise pixel-by-pixel scrolling (use passive scroll listeners).
  • Detecting overlaps of elements that do not involve the viewport/scroll container.

Code Examples

Lazy Loading Images Efficiently

This creates a single observer that tracks multiple images. As soon as an image is within 100px of the viewport, its actual source is loaded, and it is unobserved to free memory.

const observer = new IntersectionObserver((entries, obs) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      obs.unobserve(img); // Stop observing once loaded
    }
  });
}, { rootMargin: '100px' });

document.querySelectorAll('img.lazy').forEach(img => observer.observe(img));

Advantages

  • Completely asynchronous; it does not run on the main thread during scroll events.
  • Eliminates the need for expensive getBoundingClientRect() calls inside scroll event listeners.
  • Highly customizable using rootMargin to preload content before it enters the viewport.

Limitations

  • Cannot detect if an element is obscured by another visually (e.g. z-index overlap or opacity: 0).
  • Callbacks are fired asynchronously, meaning there is a slight delay (not frame-perfect for synchronized parallax).

Common Mistakes

  • Failing to unobserve or disconnect the observer after the element is loaded, causing memory leaks.
  • Creating a new IntersectionObserver instance for every single list item instead of sharing one instance with multiple targets.
  • Doing heavy DOM manipulation inside the observer callback without batching.

References