Notes on the Intersection Observer API

#Web API#Intersection Observer API#Performance

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.

developer.mozilla.org

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?)

Intersection observer

This feature is well established and works across many devices and browser versions. It’s been available across browsers since March 2019

Learn more

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
  • Infinite scrolling — Watch a sentinel element at the end of a list to trigger loading more content
  • 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!

nisshi.dev
nisshi.dev

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)

github.com

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.

web.dev

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)

github.com
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

web.dev
nisshi-dev
nisshi-dev

VRが好きなWebエンジニア。WebXRやVR・機械加工などの技術が好きでものづくりしている。WebXR JPというコミュニティやWeb技術集会というVR空間内の技術イベントを運営中。

Comments

Feel free to share your thoughts or questions about this article

Nickname:
Anonymous
Loading comments...
nisshi-dev
nisshi-dev

VRが好きなWebエンジニア。WebXRやVR・機械加工などの技術が好きでものづくりしている。WebXR JPというコミュニティやWeb技術集会というVR空間内の技術イベントを運営中。

On this page