React Hooks

useIntersection

Learn how to get the intersecting element with IntersectionObserver API

September 06, 2021

Loading views
useIntersection.js
import { useEffect, useState } from 'react';

export const useIntersection = (elementIds) => {
  const [activeId, setActiveId] = useState('');

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          if (entry.isIntersecting) {
            setActiveId(entry.target.id);
          }
        }
      },
      { rootMargin: '0px', threshold: 1 },
    );

    for (const id of elementIds) {
      observer.observe(document.getElementById(id));
    }

    return () => {
      for (const id of elementIds) {
        if (document.getElementById(id)) {
          observer.unobserve(document.getElementById(id));
        }
      }
    };
  }, [elementIds]);

  return activeId;
};

Context

Whenever you want to know what's active on your viewport, and it's determined by the scroll position. The IntersectionObserver API would be your best choice.

Usage

const elementIds = ['header-1', 'header-2', 'header-3'];

const App = () => {
  const activeId = useIntersection(elementIds);

  return (
    <div>
      {elementIds.map((e) => (
        <section key={e}>
          <h2 id={e} style={{ color: activeId === e ? 'red' : 'black' }}>
            Title
          </h2>
          {/* Long content */}
        </section>
      ))}
    </div>
  );
};

Explanation

This hook takes in an array of element IDs specific in your components. And then start observing whether an element is in view with the IntersectionObserver API. The condition of being in view, or in other words, intersecting with another element which by default is the root document.

With rootMargin: '0px', this tells the API that we are using the entire viewport. And threshold: 1 means the element has to be fully inside the viewport in order to be active. Make sure your content is long enough for each section to be spread across a scrollable view. It won't do anything if everything are already in view.

This particular implementation is best for creating a dynamic table of content. The active section would update based on the intersection result.

It can be tweaked for improving website performance. It's useful for lazy loading elements in your website. For example, start loading more content when we've scrolled to a particular container. I found this article explains the API in detail.

Thanks For Reading 😉

If you would like to show your further support:

Buy Me a Book