2 min read

Intersection Observer

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

1. The Concept

Historically, detecting when an element became visible (for lazy loading or infinite scroll) required event handlers on the main thread (like scroll or resize) and calling getBoundingClientRect(). This was slow and caused performance issues.

Intersection Observer offloads this to the browser, which can optimize it and run it off the main thread.

2. Basic Usage

To use it, you create a new IntersectionObserver object, passing it a callback function and an optional options object.

const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('Element is visible!');
      // Stop observing if needed
      // observer.unobserve(entry.target);
    }
  });
});

const target = document.querySelector('#target');
observer.observe(target);

3. Options

You can customize when the callback is invoked.

const options = {
  root: null,        // The viewport (null = browser viewport)
  rootMargin: '0px', // Margin around the root (like CSS margin)
  threshold: 0.5     // Callback runs when 50% of target is visible
};

const observer = new IntersectionObserver(callback, options);
  • root: The element that is used as the viewport for checking visibility of the target. Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null.
  • rootMargin: Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections.
  • threshold: Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed.

4. Use Cases

Lazy Loading Images

const images = document.querySelectorAll('img[data-src]');

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      observer.unobserve(img);
    }
  });
});

images.forEach(img => imageObserver.observe(img));

programming/javascript/vanilla/javascript programming/javascript/vanilla/dom-manipulation programming/javascript/vanilla/events