useIntersectionObserver()

A React hook for detecting element visibility within a container

This hook lets you detect when a given element intersects with an ancestor element, or with the document's viewport. It's using an IntersectionObserver internally to observe changes in the intersection.

This is useful when you need an element to behave differently when it's not viewed by the user.

You can use this for:

  1. Pausing intensive animations (or other expensive processes) when they are off-screen.
  2. Creating a sidebar with items that are highlighted based on the currently viewed section of the page.
  3. Creating headers that change when the user scrolls.
  4. Lazy-loading of content as it becomes visible to the user.

Implementation

Here's a basic implementation of this hook:

js
function useIntersectionObserver(target, options) {
    const [intersecting, setIntersecting] = useState();
    useEffect(() => {
        const observer = new IntersectionObserver(
            entries => {
                setIntersecting(entries[0].isIntersecting);
            },
            options
        );
        observer.observe(target.current);
        return () => observer.disconnect();
    }, []);
    return intersecting;
};

So what's going on here?

I create a IntersectionObserver instance in a useEffect with no dependencies (so it's only called once, when the component mounts).

I then call observer.observe() on the given target element, and update the intersecting state inside the callback on the first entry (since I only observe one entry - our target).

The hook returns a single boolean value, representing whether the target is intersecting the root or not.

Usage

Here's how you can use this hook:

jsx
const MyComponent = () => {
    const target = useRef();
    const intersecting = useIntersectionObserver(target, {
        root: document.body,
        rootMargin: '0px',
        threshold: 0.5
    });

    return (
        <div ref={target}>
            {intersecting ? 'Visible' : 'Not visible'}
        </div>
    );
};

In the example above I observe intersections between a given div and the viewport (i.e document.body). A threshold of 0.5 means that the value of intersecting becomes true only when at least 50% of the element is inside the container.

Live example

Scroll down/up to see the div change colors as it enters/leaves the container visible area (i'm using a threshold of 0.8 here)

Scroll down
Not intersecting