What IntersectionObserver detects

IntersectionObserver reports when an element’s visible area intersects a chosen boundary. That boundary can be the browser viewport or a scrollable ancestor. It is useful for tasks such as starting a decorative effect, recording that a section has come into view, or preparing content shortly before it appears.

Unlike a scroll event handler, an observer does not require your code to check every target’s position on every scroll event. You give it targets and a callback; the browser calls that function when an observed target crosses a configured intersection threshold. The callback can also run soon after observation begins, so an element already on screen can be reported without the user scrolling.

Intersection is a geometric signal, not proof that someone has seen or read an element. Another element might cover the target, for example. Use the API to respond to position, and avoid treating a callback as a reliable measure of attention.

A reproducible viewport-entry example

This example uses plain JavaScript in a modern browser. Add several elements with the class watch-section to an HTML page, then place the following script after those elements or run it once the DOM is ready. The observer adds a class while each element intersects the viewport and removes it when the element leaves.

const targets = document.querySelectorAll('.watch-section'); const observer = new IntersectionObserver((entries) => { for (const entry of entries) { entry.target.classList.toggle( 'in-viewport', entry.isIntersecting ); } }, { threshold: 0 }); targets.forEach((target) => observer.observe(target));

You can test the result in your browser’s developer tools by scrolling and checking whether in-viewport appears on each element. A CSS rule can use that class for a subtle visual change. Keep the content readable without the class: essential text should not depend on scrolling or JavaScript to become available. If the class controls motion, consider the user’s reduced-motion preference.

Choose the boundary and threshold

The observer in the example omits root, which means it uses the browser viewport. For an element inside a scrollable panel, set root to that panel element instead. The target must be within the root’s descendant tree. This makes the same API useful for both page scrolling and scrolling inside a component.

The threshold option describes how much of the target must intersect before a threshold crossing is reported. A value of 0 is suited to detecting first contact. A value of 0.5 represents half the target’s area; an array such as [0, 0.5, 1] asks for reports at several crossings. If you need an action specifically at 50%, check entry.intersectionRatio >= 0.5 in the callback rather than relying on entry.isIntersecting alone, which only indicates an intersection.

The rootMargin option expands or shrinks the root’s boundary using CSS-like offsets. For example, rootMargin: '200px 0px' can report a target before it reaches the viewport, which may help prepare content ahead of scrolling. A negative margin moves the effective boundary inward. Choose margins for the behavior you want, then test them with targets of different sizes.

Handle entries and stop observing when appropriate

A callback receives an array because several targets may cross thresholds around the same time. Each entry identifies its target and includes information such as isIntersecting and intersectionRatio. Process every entry rather than assuming the callback describes only one element. Also remember that a target can trigger callbacks repeatedly as it enters and leaves; the example deliberately updates the class in both directions.

Some tasks need only the first entry. After successfully handling such a target, call observer.unobserve(entry.target) inside the callback. This stops reports for that target while leaving other targets observed. If a component is removed entirely, call observer.disconnect() during its cleanup to stop observing all of that observer’s targets. These choices keep the observer’s lifetime aligned with the interface it serves.

Test the behavior, not just the callback

Try the example with an element initially on screen, another below the fold, and one taller than the viewport. Scroll in both directions and resize the window. These cases help distinguish “any intersection” from “mostly visible,” especially when experimenting with thresholds. In a scrollable panel, verify that the configured root is the panel you intend to watch.

Finally, decide what should happen when the API or JavaScript is unavailable. A robust page can show its core content by default and use observation only as an enhancement. IntersectionObserver is most useful when the position of an element genuinely matters; it need not be added to content that works just as well without viewport detection.

Back to category ↗