A thin wrapper around the IntersectionObserver API. It observes a target element and fires a callback when the element intersects with the viewport. Includes a graceful fallback for environments where the API isn’t available, plus a stop() function to disconnect the observer when you’re done.
export interface IntersectionObserverOptions {
/**
* The Element or Document whose bounds are used as the bounding box when testing for intersection.
* The element that is used as the viewport for checking visibility of the target.
* Must be the ancestor of the target. Defaults to the browser viewport if not specified or if null.
*/
root?: Element | Document
/**
* A string which specifies a set of offsets to add to the root's bounding_box when calculating intersections.
*/
rootMargin?: string
/**
* Either a single number or an array of numbers between 0.0 and 1.
* A threshold of 1.0 means that when 100% of the target is visible within the element specified by the root option, the callback is invoked.
*/
threshold?: number | number[]
}
export function useIntersectionObserver(
target: Element,
callback: IntersectionObserverCallback,
options: IntersectionObserverOptions = {},
): { isSupported: boolean; stop: () => void } {
const { root, rootMargin = '0px', threshold = 0.1 } = options
const isSupported = typeof window !== 'undefined' && 'IntersectionObserver' in window
if (!isSupported) {
return { isSupported, stop: () => {} }
}
const observer = new IntersectionObserver(callback, {
root,
rootMargin,
threshold,
})
observer.observe(target)
return {
isSupported,
stop: () => {
observer.disconnect()
},
}
}Usage example
Watching .target-element and logging when it enters or leaves the viewport:
import { useIntersectionObserver } from './use-intersection-observer'
const targetElement = document.querySelector('.target-element')
const callback: IntersectionObserverCallback = (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log('Element is in the viewport')
} else {
console.log('Element is out of the viewport')
}
})
}
if (targetElement instanceof Element) {
const { stop } = useIntersectionObserver(targetElement, callback, { threshold: 0.2 })
// Call stop() when you no longer need observation.
// stop()
}