🚸 IntersectionObserver
21 Jan, 2026
On this page
Basic usage Jump to heading
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log('Element is visible')
// entry.target - the observed element
// entry.intersectionRatio - how much is visible (0-1)
// entry.boundingClientRect - element's bounding box
}
})
},
{
root: null, // viewport
rootMargin: '0px',
threshold: 0.5, // trigger at 50% visibility
}
)
observer.observe(document.querySelector('.element'))
// Clean up when done
observer.disconnect()
Multiple thresholds Jump to heading
const observer = new IntersectionObserver(callback, {
threshold: [0, 0.25, 0.5, 0.75, 1], // trigger at these visibility percentages
})
Observe once (lazy loading pattern) Jump to heading
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadImage(entry.target)
observer.unobserve(entry.target) // stop observing after first trigger
}
})
})
With React Jump to heading
Using a ref Jump to heading
import { useEffect, useRef } from 'react'
const LazySection = ({ children }: { children: React.ReactNode }) => {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const element = ref.current
if (!element) return
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
console.log('Section is visible')
}
},
{ threshold: 0.5 }
)
observer.observe(element)
return () => observer.disconnect()
}, [])
return <div ref={ref}>{children}</div>
}
Custom hook Jump to heading
import { useEffect, useRef, useState } from 'react'
const useIntersectionObserver = (options?: IntersectionObserverInit) => {
const ref = useRef<HTMLElement>(null)
const [isIntersecting, setIsIntersecting] = useState(false)
useEffect(() => {
const element = ref.current
if (!element) return
const observer = new IntersectionObserver(([entry]) => {
setIsIntersecting(entry.isIntersecting)
}, options)
observer.observe(element)
return () => observer.disconnect()
}, [options])
return { ref, isIntersecting }
}
// Usage
const MyComponent = () => {
const { ref, isIntersecting } = useIntersectionObserver({ threshold: 0.5 })
return (
<div ref={ref}>
{isIntersecting ? 'Visible!' : 'Not visible'}
</div>
)
}
Trigger once Jump to heading
const useIntersectionOnce = (options?: IntersectionObserverInit) => {
const ref = useRef<HTMLElement>(null)
const [hasIntersected, setHasIntersected] = useState(false)
useEffect(() => {
const element = ref.current
if (!element || hasIntersected) return
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setHasIntersected(true)
observer.disconnect()
}
}, options)
observer.observe(element)
return () => observer.disconnect()
}, [options, hasIntersected])
return { ref, hasIntersected }
}
References Jump to heading
← Back home