🚸 IntersectionObserver

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