How to use IntersectionObserver to detect the position in…
When working with web we often need to animate or reveal html elements by detecting the scroll position. One such example would be to display a container for a state variable and scroll. The IntersectionObserver is a useful interface to achieve such functionality.
01. Using IntersectionObserver
IntersectionObserver has a two main parmeters
let observer = new IntersectionObserver(callback, options);
options = {
root // element for checking root visibility
rootMargin // margins around the root for visibility
threshold // A number of percentage of visibility or an array of points as a percentage. For 100% its 1.0 and for 25% its .25
}
For our example we will use root: null
and root-margins: '0px',
When root is null it will take the browser viewport as the default root. threshold: 0.5
will be used to load the animation when 50% is loaded.
02. Using useCallback with state variables
We need to show the animation only once when the content is showed. For this I will be using a state variable. The state will be set when callback function is triggerred.
const [isFocused, setFocused] = useState(false); const onObserved = useCallback((entries) => { const [entry] = entries; !isFocused && setFocused(entry.isIntersecting); }, []);
Here we have to enclose the callback with useCallback
to ensure the function is initialized once. Unless the removing after effect is over will not be successful as it creates more of the reference.
Here we have taken the first element from the entries array and checked isIntersecting
property to set animating variable as true
03. Using useEffect to register observer
If you can remember we had used root: null
for the observer. But we need to show the content only when this particular view is loaded. For that we can get the outer container ref to a variable and register the observer to it.
export const AnimatedWrapper = () => { const containerRef = useRef(null); // ... return ( <div ref={containerRef}> <AnimatedView isFocused={isFocused} /> </div> ); }
Now to register the observer we will implement useEffect
hook.
useEffect(() => { const observer = new IntersectionObserver(onObserved, { root: null, rootMargin: '0px', threshold: 0.5, }); let current = null; if (containerRef?.current) { current = containerRef.current; observer.observe(current); } return () => { if (current) { observer.unobserve(current); } } }, [containerRef, onObserved]);
This hook will be called when the containerRef
and onObserved
is changed. Also note that the return function will unobserve the observer
when effect is over.
Full implementation code as follows. The isFocused
field is passed to the animated container to make sure that the animation is loaded when the view is focused.
import React, {useRef, useState, useEffect, useCallback} from 'react'; export const AnimatedWrapper = () => { const containerRef = useRef(null); const [isFocused, setFocused] = useState(false); const onObserved = useCallback((entries) => { const [entry] = entries; !isFocused && setFocused(entry.isIntersecting); }, []); useEffect(() => { const observer = new IntersectionObserver(onObserved, { root: null, rootMargin: '0px', threshold: 0.5, }); let current = null; if (containerRef?.current) { current = containerRef.current; observer.observe(current); } return () => { if (current) { observer.unobserve(current); } } }, [containerRef, onObserved]); return ( <div ref={containerRef}> <AnimatedView isFocused={isFocused} /> </div> ); }; export default AnimatedWrapper;
You can find the API document here https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API