# The useIntersectionObserver
hook
This React Hook can be used to detect visibility of a component on the viewport using the 🔗 IntersectionObserver API natively present in the browser.
Some of the use cases include:
- Lazy-loading of images
- Infinite scrolling
- Start animations
Our hook will take two arguments of the following type:
{
options?: Omit<IntersectionObserverInit, 'root'>;
observerCallback: IntersectionObserverCallback;
}
We will use observerCallback
to define behaviour on intersection with viewport. It has a type of IntersectionObserverCallback
which is defined as an interface as follows:
interface IntersectionObserverCallback {
(entries: IntersectionObserverEntry[], observer: IntersectionObserver): void;
}
Finally, our hook returns an object with two properties:
{
observable: (node: Element) => void;
root: (node: Element) => void;
}
We define three reference variables:
-
nodeRefs
- To hold references for observable nodes as an array of reference objects -
observerRef
- To hold reference for intersection observer -
rootRef
- To hold reference for root node with which observables intersect
export function useIntersectionObserver({
options = { rootMargin: '0px', threshold: [0] },
observerCallback,
}: {
options?: Omit<IntersectionObserverInit, 'root'>;
observerCallback: IntersectionObserverCallback;
}) {
const nodeRefs = useRef<Element[]>([]);
const rootRef = useRef<Element>(null);
const observerRef = useRef<IntersectionObserver | null>(null);
...
};
Then, we define rootRefCallback
and observableRefCallback
mapped to root
and observable
respectively as follows:
‼️ These are used to set the root and observable nodes for intersection observer.
const observableRefCallback = useCallback<(node: Element) => void>(
(node) => {
nodeRefs.current.push(node);
initializeObserver();
},
[initializeObserver]
);
const rootRefCallback = useCallback<(node: Element) => void>(
(rootNode) => {
rootRef.current = rootNode;
initializeObserver();
},
[initializeObserver]
);
In both cases, once the root and observable nodes are set, we initialize the intersection observer with function initializeObserver
. This function basically resets the intersection observer as follows:
const unobserve = useCallback(() => {
// stops watching all of its target elements for visibility changes.
observerRef.current?.disconnect();
// sets to null
observerRef.current = null;
}, []);
const observe = useCallback(() => {
// If observable nodes exist
const nodes = nodeRefs.current;
if (nodes.length > 0) {
const root = rootRef.current;
const { rootMargin, threshold } = options;
// Create a new IntersectionObserver instance
const observer = new IntersectionObserver(observerCallback, {
root,
rootMargin,
threshold,
});
// Observe all observable nodes
nodes.forEach((node) => observer.observe(node));
// Set observer reference
observerRef.current = observer;
}
}, [options.rootMargin, options.threshold]);
const initializeObserver = useCallback(() => {
unobserve();
observe();
}, [observe, unobserve]);
# Using the intersection observer hook
Define observer callback
const observerCallback = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
entries.forEach((entry) => {
...
});
};
Call useIntersectionObserver
to get observable and root callbacks
const { observable, root } = useIntersectionObserver({
observerCallback,
});
Attach observable to observable nodes
React.useEffect(() => {
document.querySelectorAll('.anim').forEach(observable);
}, []);
Attach root to root node
<div ref={root}>
<div className="anim">...</div>
<div className="anim">...</div>
<div className="anim">...</div>
</div>
# References
🤩 Scroll down on the browser in the playground to see the animation.
Top comments (0)