Tracking the component’s visibility can be really handy in multiple cases, especially for performance, when you want to load to the media like image, video, audio, etc. only when the component is in the viewport or is visible.
Another case is when you want to track the user activity like when the user is a starring a product (product is in the viewport) so that you can use this data for recommendations.
For this, we can create a useOnScreen() hook that will return a boolean value if the component or HTML element is the viewport or not in Reactjs.
There are two ways to implement it.
1. Using Intersection Observer.
2. Using getBoundingClientRect().
Using Intersection Observer
With useRef()
we will create a reference to the DOM element which we want to track, and then pass this to the useOnScreen()
hook.
useOnScreen()
hook will set up the observation for the ref when the component will be mounted. This is will be done in the useEffect()
and then create an instance of IntersectionObserver and if the entry is interacting, update the state to true
which means it visible, otherwise false
.
Disconnect the observation when the component is about to unmount inside the useEffect()
.
function useOnScreen(ref) { const [isIntersecting, setIntersecting] = useState(false); // monitor the interaction const observer = new IntersectionObserver( ([entry]) => { // update the state on interaction change setIntersecting(entry.isIntersecting); },{ threshold: 1.0 } ); useEffect(() => { // assign the observer observer.observe(ref.current); // remove the observer as soon as the component is unmounted return () => { observer.disconnect(); }; }, []); return isIntersecting; }
Test Case
const Element = ({ number }) => { const ref = useRef(); const isVisible = useOnScreen(ref); return ( <div ref={ref} className="box"> {number} {isVisible ? `I am on screen` : `I am invisible`} </div> ); }; const DummyComponent = () => { const arr = []; for (let i = 0; i < 20; i++) { arr.push(<Element key={i} number={i} />); } return arr; }; export default DummyComponent;
Using getBoundingClientRect()
Unlike Intersection Observer, here we will have to perform simple calculations to determine if the element is in the viewport or not.
If the top of the element is greater than zero but less than the window.innerHeight
then it is in the viewport. We can also add some offset in case we want a buffer.
Assign a scroll event on the window and inside the listener get the getBoundingClientRect() of the element. Perform the calculation and update the state accordingly.
function useOnScreen2(ref) { const [isIntersecting, setIntersecting] = useState(false); // determine if the element is visible const observer = function () { const offset = 50; const top = ref.current.getBoundingClientRect().top; setIntersecting(top + offset >= 0 && top - offset <= window.innerHeight); }; useEffect(() => { // first check observer(); // assign the listener window.addEventListener("scroll", observer); // remove the listener return () => { window.removeEventListener("scroll", observer); }; }, []); return isIntersecting; }
Test Case
const Element = ({ number }) => { const ref = useRef(); const isVisible = useOnScreen2(ref); return ( <div ref={ref} className="box"> {number} {isVisible ? `I am on screen` : `I am invisible`} </div> ); }; const DummyComponent = () => { const arr = []; for (let i = 0; i < 20; i++) { arr.push(<Element key={i} number={i} />); } return arr; }; export default DummyComponent;