In our previous post, we covered the basics of the IntersectionObserver API, including its syntax and common methods. The good news is that we don't need to rely on any external libraries to use this API with React. By using the built-in React hooks, we can handle all the intersection observer functionality with ease.
In this post, we'll dive into how to use the IntersectionObserver API in React.
Checking if an element is partially visible
When working with React, it's not recommended to use DOM manipulation methods like querySelector
and querySelectorAll
to retrieve elements. Instead, we can use the useRef
hook.
If you're new to
useRef
, I recommend checking out this series which covers basic and advanced usage of React refs with real-world examples.
To start, we create a ref using the useRef
hook to reference the specific DOM element we want to observe for visibility changes. We attach the target element to the ref using the ref
attribute.
Then, we set up a state variable called isVisible
using the useState
hook with an initial value of false
.
const elementRef = React.useRef();
const [isVisible, setIsVisible] = React.useState(false);
// Render
return (
<div ref={elementRef}>...</div>
);
Next, we're going to use the useEffect
hook to set up an IntersectionObserver
. This is a browser API that lets us know when an element becomes visible or hidden on the page. But before we can use the API, we need to get the element we want to observe.
To do this, we used the useRef
hook to create a reference to the element. Then, we access the current
property of the reference to get the actual element.
Once we have the element, we create an observer instance using the IntersectionObserver
constructor. We pass in a callback function that will be called whenever the element intersects with the viewport.
The callback function receives an array of entries
, but we only care about the first one. So, we use array destructuring to get it. This entry
object contains information about whether or not the element is currently visible.
If the element is visible, we set our isVisible
state variable to true
. If it's not visible, we set isVisible
to false
. Finally, we call the observer's observe
method with our target element.
To clean up after ourselves, we return a function from the useEffect
hook that calls the observer's unobserve
method on our target element. This ensures that when our component unmounts or the element is removed from the page, we stop observing it.
Here's an example of how to use the useEffect
hook to set up an IntersectionObserver
.
React.useEffect(() => {
const ele = elementRef.current;
if (!ele) {
return;
}
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
});
observer.observe(ele);
return () => {
observer.unobserve(ele);
};
}, []);
Lastly, we create a div
element that shows whether or not the observed element is partially visible.
// Render
return (
<div>
{isVisible ? 'Element is partially visible' : 'Element is not partially visible'}
</div>
);
Check out the demo below. Scroll up and down to see how the message updates automatically, letting you know if the target element is visible or not.
Checking if an element is fully visible
To ensure that an element is fully visible, we can update our implementation using the intersectionRatio
property of the entry
object passed to the IntersectionObserver's callback function.
The intersectionRatio
property is a value between 0 and 1 that represents the percentage of the targeted element that is currently visible within the viewport. If this value is equal to 1, then the entire element is visible.
To achieve this, we can modify our existing code by checking whether entry.intersectionRatio
is equal to 1 or not. If it's equal to 1, then we can set our state variable isVisible
to true
, indicating that the element is fully visible. Otherwise, we set it to false
.
Here's an example implementation:
React.useEffect(() => {
const ele = elementRef.current;
if (!ele) {
return;
}
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.intersectionRatio === 1);
});
observer.observe(ele);
return () => {
observer.unobserve(ele);
};
}, []);
Although it may seem promising and workable in theory, in practice, it falls short. Go ahead and try scrolling down in the playground below. You'll notice that the message doesn't update even if you scroll over the entire element.
The reason why the approach to check if an element is fully visible doesn't work is because intersectionRatio
only considers when the element is partially or not visible at all.
We can fix this issue by adding an extra option to the IntersectionObserver
constructor. This option is called threshold
, and it lets us specify at what percentage of visibility we want our callback function to be executed.
To make sure our callback function is called when the observed element is fully visible, we can set the threshold
value to 1. This means that our callback function will only be executed when the entire observed element is visible in the viewport.
It's worth noting that the threshold
option can also accept an array of numbers. Here's how we can modify our existing code to implement this solution:
React.useEffect(() => {
const ele = elementRef.current;
if (!ele) {
return;
}
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
}, {
threshold: 1,
});
observer.observe(ele);
return () => {
observer.unobserve(ele);
};
}, []);
With this modification, the isVisible
state variable now accurately shows whether or not the observed element is fully visible. Give it a try by scrolling up and down in the playground below to see how this updated implementation works.
Conclusion
In this post, we learned how to use the IntersectionObserver API with React. By encapsulating all the intersection observer functionality within the useEffect
hook, we were able to check if an element is partially or fully visible using different threshold values.
Using the IntersectionObserver API can significantly improve an application's performance by reducing the number of calculations required to determine visibility changes. In our next post, we'll take it a step further and learn how to turn the implementation into a reusable hook and component.
If you want more helpful content like this, feel free to follow me:
Top comments (0)