useIntersectionObserver
Detect when an element enters or exits the viewport using the Intersection Observer API.
Approach 1: Multiple Observers
Each element gets its own observer instance. Simple and independent tracking.
Scroll inside the box to see multiple elements being tracked independently
Box 1: ❌ Hidden
Box 2: ❌ Hidden
Box 3: ❌ Hidden
Approach 2: Single Observer
One observer tracks multiple elements. More efficient for many elements.
Single Observer tracking multiple elements
More efficient for tracking many elements
Box 1: ❌ Hidden
Box 2: ❌ Hidden
Box 3: ❌ Hidden
Code Examples
Multiple Observers (Simple)
import { useIntersectionObserver } from "@uiblock/hooks";
export default function Demo() {
// Each call creates a separate observer
const [ref1, isVisible1] = useIntersectionObserver();
const [ref2, isVisible2] = useIntersectionObserver();
const [ref3, isVisible3] = useIntersectionObserver();
return (
<div>
<div ref={ref1}>Box 1: {isVisible1 ? 'Visible' : 'Hidden'}</div>
<div ref={ref2}>Box 2: {isVisible2 ? 'Visible' : 'Hidden'}</div>
<div ref={ref3}>Box 3: {isVisible3 ? 'Visible' : 'Hidden'}</div>
</div>
);
}Single Observer (Efficient)
import { useIntersectionObserverMultiple } from "@uiblock/hooks";
import { useRef, useEffect } from "react";
export default function Demo() {
const { observe, isVisible } = useIntersectionObserverMultiple();
const box1Ref = useRef(null);
const box2Ref = useRef(null);
const box3Ref = useRef(null);
useEffect(() => {
if (box1Ref.current) observe(box1Ref.current, 'box1');
if (box2Ref.current) observe(box2Ref.current, 'box2');
if (box3Ref.current) observe(box3Ref.current, 'box3');
}, [observe]);
return (
<div>
<div ref={box1Ref}>Box 1: {isVisible('box1') ? 'Visible' : 'Hidden'}</div>
<div ref={box2Ref}>Box 2: {isVisible('box2') ? 'Visible' : 'Hidden'}</div>
<div ref={box3Ref}>Box 3: {isVisible('box3') ? 'Visible' : 'Hidden'}</div>
</div>
);
}When to Use Each
Multiple Observers
- Simple, independent tracking
- Few elements (1-10)
- Each element has unique logic
- Easier to understand
Single Observer
- Better performance
- Many elements (10+)
- Lazy loading lists/grids
- Lower memory usage
How It Works
Here's the implementation:
import { useEffect, useRef, useState } from 'react'
export function useIntersectionObserver(options?) {
const [isVisible, setIsVisible] = useState(false)
const ref = useRef(null)
useEffect(() => {
const element = ref.current
if (!element) return
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting)
}, options)
observer.observe(element)
return () => observer.disconnect()
}, [options])
return [ref, isVisible]
}Multiple Elements Version
For tracking multiple elements with a single observer:
import { useEffect, useRef, useState, useCallback } from 'react'
export function useIntersectionObserverMultiple(options?) {
const [visibilityMap, setVisibilityMap] = useState(new Map())
const observerRef = useRef(null)
useEffect(() => {
observerRef.current = new IntersectionObserver((entries) => {
setVisibilityMap((prev) => {
const newMap = new Map(prev)
entries.forEach((entry) => {
const id = entry.target.getAttribute('data-observe-id')
if (id) {
newMap.set(id, entry.isIntersecting)
}
})
return newMap
})
}, options)
return () => observerRef.current?.disconnect()
}, [options])
const observe = useCallback((element, id) => {
if (!element || !observerRef.current) return
element.setAttribute('data-observe-id', id)
observerRef.current.observe(element)
}, [])
const isVisible = useCallback(
(id) => visibilityMap.get(id) ?? false,
[visibilityMap]
)
return { observe, isVisible, visibilityMap }
}API Reference
useIntersectionObserver
Parameters
options?: IntersectionObserverInitOptional configuration (threshold, root, rootMargin)
Returns
[ref, isVisible] - Ref to attach to element and boolean visibility state
useIntersectionObserverMultiple
Parameters
options?: IntersectionObserverInitOptional configuration (threshold, root, rootMargin)
Returns
observe(element, id) - Function to observe an element with a unique ID
isVisible(id) - Function to check if element with ID is visible
visibilityMap - Map of all element IDs and their visibility states
💡 Use Cases
- Lazy loading images when they enter viewport
- Triggering animations on scroll
- Infinite scroll pagination
- Analytics tracking for element visibility