Notes on the Intersection Observer API
Translation Note
This article was translated from Japanese with the help of Claude Opus 4.6. For the original content, please refer to: Intersection Observer APIのメモ
What Is This API?
It's an API that asynchronously detects when an element enters or leaves the viewport (or a specified parent element).
It's much more performant and reliable than attaching a scroll event listener in useEffect and calculating positions manually in React.
It's supported in pretty much every browser, so you can use it without worry. (IE11 and earlier don't support it, but... we don't need to care about that anymore, right? ...right?)
This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2019
Basic Usage
Create an observer with new IntersectionObserver(callback, options) and start observing with .observe(element). You can check whether an element is visible using entry.isIntersecting in the callback.
Common Use Cases
- Lazy loading — Load images, 3D models, etc. near the viewport, and unload them after they pass
- Library example: model-viewer (viewport detection for 3D models)
- Scroll-triggered animations — Fade in elements as they enter the screen
- Library examples: Motion's
whileInView, Locomotive Scroll (Lenis-based)
- Library examples: Motion's
- Infinite scrolling — Watch a sentinel element at the end of a list to trigger loading more content
- Library example: react-infinite-observer
- Section detection — Highlight the current section in a table of contents
- Library example: Fumadocs TOC (also used in this blog's table of contents)
This blog also uses this API for embedding various content. For example, pages with lots of embedded tweets and 3D models like the ones below would crash Mobile Safari without this API. Thanks to it, no more crashes!
rootMargin Is Super Handy
You can start detection from outside the viewport (ahead of time). For example, setting 200px fires the callback 200px before the element enters the screen, so by the time the user scrolls to it, loading is already done.
const observer = new IntersectionObserver(callback, {
rootMargin: "200px", // Detect 200px outside the viewport
});The optimal value depends on the type of content and context. Looking at real code, image lazy loading often uses around 50px, while model-viewer sets it to 0px for 3D model rendering to avoid off-screen animations consuming rendering resources. (According to Claude's research)
By the way, for native lazy loading with <img loading="lazy">, Chrome's internal thresholds are quite large — 1250px on 4G connections and 2500px on 3G or slower.
Using It in React
In a React environment, useInView from react-intersection-observer is really easy to use.
It's actively maintained, has reached v10, and was last updated just a month ago. (As of February 18, 2026)
import { useInView } from "react-intersection-observer";
function LazyComponent() {
const { ref, inView } = useInView({
triggerOnce: true, // Fire only once (great for lazy loading)
rootMargin: "200px", // Start loading ahead of time
});
return (
<div ref={ref}>
{inView ? <HeavyContent /> : <Skeleton />}
</div>
);
}With triggerOnce: true, it automatically calls unobserve() internally, so one-way lazy loading is handled with just this.
References
VRが好きなWebエンジニア。WebXRやVR・機械加工などの技術が好きでものづくりしている。WebXR JPというコミュニティやWeb技術集会というVR空間内の技術イベントを運営中。
Comments
Feel free to share your thoughts or questions about this article