react-intersection-observer is a powerful React hook and component library that provides an easy-to-use wrapper around the native Intersection Observer API. The Intersection Observer API is a browser API that allows you to asynchronously observe changes in the intersection of a target element with an ancestor scroll container or with the top-level document's viewport.
Why use react-intersection-observer?
It simplifies common use cases such as:
* Lazy Loading: Loading images, videos, or components only when they are about to enter the viewport, improving initial page load performance.
* Infinite Scrolling: Triggering the loading of more content when a user scrolls near the bottom of a list.
* Animation Triggers: Starting animations or visual effects when an element becomes visible.
* Tracking Element Visibility: Knowing when an element is in or out of view for analytics or other purposes.
How it works:
The library primarily exposes the `useInView` hook (for functional components) and an `<InView>` component (for class components or when render props are preferred). You attach a `ref` (provided by the hook/component) to the DOM element you want to observe. When that element's visibility changes relative to its root (viewport by default), a callback is triggered, and the `inView` state (a boolean indicating visibility) is updated.
Key Features & Props (primarily for `useInView` hook):
* `useInView()` hook: The most common way to use it in functional components. It returns an array `[ref, inView, entry]`.
* `ref`: A React ref that must be attached to the DOM element you want to observe.
* `inView`: A boolean indicating whether the observed element is currently intersecting with the root.
* `entry`: The `IntersectionObserverEntry` object, containing detailed information about the intersection change.
* `<InView>` component: Provides similar functionality using a render prop (`{({ inView, ref, entry }) => ...}`).
* `threshold`: A number or an array of numbers between 0 and 1, indicating the percentage of the target element's visibility at which the observer's callback should be executed.
* `0`: Trigger as soon as one pixel is visible.
* `1`: Trigger only when the entire element is visible.
* `[0, 0.25, 0.5, 0.75, 1]`: Trigger at these specific visibility percentages.
* `root`: The element that is used as the viewport for checking intersection. Defaults to the browser viewport.
* `rootMargin`: A margin around the `root` element, extending or shrinking its effective intersection area. Similar to CSS `margin` properties (e.g., "10px 20px 30px 40px").
* `triggerOnce`: If `true`, the observer will unobserve the target element once it has intersected the root at least once. Useful for one-shot animations or lazy loading.
* `onChange`: An optional callback function that is fired whenever the `inView` status changes, receiving `(inView, entry)` as arguments.
By abstracting away the complexities of the native API and integrating seamlessly with React's component lifecycle and state management, `react-intersection-observer` makes it significantly easier to build performant and responsive user interfaces.
Example Code
import React from 'react';
import { useInView } from 'react-intersection-observer';
// Basic styling (can be in App.css or inline)
const styles = {
app: {
fontFamily: 'sans-serif',
textAlign: 'center',
margin: 0,
padding: 0,
},
spacer: {
height: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f5f5f5',
},
placeholderBox: {
height: '50vh',
backgroundColor: '#e0e0e0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: '20px',
},
imageContainer: {
minHeight: '400px', // Prevent layout shift
backgroundColor: '#f9f9f9',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginBottom: '20px',
},
skeletonLoader: {
width: '600px',
height: '400px',
backgroundColor: '#cccccc',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#666',
fontSize: '1.2em',
},
observedBox: {
height: '300px',
width: '80%',
maxWidth: '600px',
margin: '40px auto',
backgroundColor: '#add8e6', // Light blue
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '8px',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
opacity: 0,
transform: 'translateY(20px)',
transition: 'opacity 0.5s ease-out, transform 0.5s ease-out, background-color 0.5s ease-out',
},
observedBoxVisible: {
opacity: 1,
transform: 'translateY(0)',
backgroundColor: '#90ee90', // Light green when visible
},
heading: {
marginTop: '50px',
},
};
function App() {
// Hook for lazy loading an image
const { ref: imageRef, inView: imageInView } = useInView({
threshold: 0, // Trigger as soon as any part of the image is visible
triggerOnce: true, // Only trigger once, then unobserve
});
// Hook for observing a general box with specific options
const { ref: boxRef, inView: boxInView } = useInView({
threshold: 0.5, // Trigger when 50% of the box is visible
rootMargin: '0px 0px -100px 0px', // Shrink the root's bottom margin by 100px
});
return (
<div style={styles.app}>
<h1 style={styles.heading}>react-intersection-observer Example</h1>
<div style={styles.spacer}>
<h2>Scroll down to see the magic!</h2>
</div>
<div style={styles.placeholderBox}>
<p>This is a placeholder before the observed image.</p>
</div>
<div ref={imageRef} style={styles.imageContainer}>
{imageInView ? (
<img
src="https://via.placeholder.com/600x400?text=Lazy+Loaded+Image"
alt="Lazy Loaded Content"
style={{ maxWidth: '100%', height: 'auto' }}
/>
) : (
<div style={styles.skeletonLoader}>
Loading image...
</div>
)}
</div>
<div style={styles.placeholderBox}>
<p>Another placeholder before the observed box.</p>
</div>
<div
ref={boxRef}
style={{
...styles.observedBox,
...(boxInView ? styles.observedBoxVisible : {}),
}}
>
{boxInView ? (
<p>
Hello from the observed box! I am 50% in view.
<br />
<code>inView</code> is: {String(boxInView)}
</p>
) : (
<p>Scroll down to see this box appear!</p>
)}
</div>
<div style={styles.spacer}>
<h2>End of example.</h2>
</div>
</div>
);
}
export default App;








react-intersection-observer