Tutorial · html-css-js · Published 2026-08-16 · 3 min read

IntersectionObserver API for scroll-triggered effects

Use the IntersectionObserver API to detect elements entering or leaving the viewport for lazy loading, animations and analytics.

Flat editorial illustration showing wireframe bricks and translucent layer panels assembling into a small browser window outline with dual panes.
Illustration: this article at a glance.

What it does

The IntersectionObserver API lets JavaScript ask the browser to notify it when a watched element's visibility relative to the viewport (or another container) changes. Instead of attaching a scroll listener and computing positions yourself on every scroll event, you register an observer and the browser reports intersection changes efficiently at the point they happen.

Editorial close-up illustration showing wireframe bricks and translucent layer panels assembling into a small browser window outline with dual panes.
Illustration: a closer look at the technique described above.

It is Baseline Widely Available, so it works across all current engines with no polyfill. The core pattern is declarative: create an observer with a callback and options, then call observe() on each target, and the callback fires initially (to report the starting state) and whenever the element crosses the configured threshold. Observers run asynchronously, so you do not read scroll positions manually; this is what makes them both simpler and cheaper than a naive scroll handler.

Watch an element

A minimal observer reports when an element enters and leaves the viewport:

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      console.log("Element is in view", entry.target);
    }
  });
}, { threshold: 0.2 });

observer.observe(document.querySelector(".reveal"));

The root option changes the container to observe against (the viewport is the default), threshold is the fraction of the target that must intersect before the callback fires (single number or array), and rootMargin expands or shrinks the observed box so you can trigger slightly before the element is actually visible.

For lazy content, entry.isIntersecting tells you when to start loading; for animations, the same signal marks when to add a visible class. In all cases, unobserve the target once the condition is met so the observer stops tracking it, which avoids work that is no longer needed.

Real-world uses

Where it crosses into performance, the observer is often the trigger that replaces a scroll listener. If scroll handlers are doing the heavy lifting, moving to an observer keeps the event work off the main thread by eliminating per-frame position recalculations. Combine it with built-in lazy loading for simple images (which cover the common case with an attribute) and reserve an observer for the cases that need actual JavaScript, such as deferred third-party widgets or analytics visibility, so the intersection behaviour stays simple and the page stays responsive.

Need a website built, fixed, optimised, migrated or replaced?

This technical resource is written by CSMBAC, a small design and development studio. If you would rather hand the problem to a professional, the website service page explains how we build enquiry-ready websites.

Explore website services