Skip to main content

Overview

IntersectionObserver composable for detecting when an element is visible in the viewport. Perfect for lazy loading, infinite scroll, and visibility detection.

Features

  • IntersectionObserver API wrapper
  • Pause/resume/stop functionality
  • Automatic cleanup on unmount
  • SSR-safe (checks SUPPORTS_INTERSECTION_OBSERVER)
  • Hydration-aware
  • Immediate callback option
  • Configurable threshold and root margin

Function Signature

function useIntersectionObserver(
  target: MaybeElementRef,
  callback: (entries: IntersectionObserverEntry[]) => void,
  options?: IntersectionObserverOptions
): UseIntersectionObserverReturn

Parameters

target
MaybeElementRef
The element to observe.
callback
(entries: IntersectionObserverEntry[]) => void
The callback to execute when the element’s intersection changes.
options
IntersectionObserverOptions
immediate
boolean
default:"false"
Call the callback immediately with synthetic data.
once
boolean
default:"false"
Stop observing after first intersection.
root
Element | null
The element used as the viewport. Defaults to browser viewport.
rootMargin
string
default:"'0px'"
Margin around the root. Can be CSS margin values.
threshold
number | number[]
default:"0"
Percentage of target visibility at which to trigger callback.

Return Value

isIntersecting
Readonly<Ref<boolean>>
Whether the target element is currently intersecting with the viewport.
isActive
Readonly<Ref<boolean>>
Whether the observer is currently active.
isPaused
Readonly<Ref<boolean>>
Whether the observer is currently paused.
pause
() => void
Pause observation.
resume
() => void
Resume observation.
stop
() => void
Stop observation and clean up.

Examples

Basic visibility detection

import { ref } from 'vue'
import { useIntersectionObserver } from '@vuetify/v0'

const target = ref<HTMLElement>()
const isVisible = ref(false)

const { isIntersecting, pause, resume } = useIntersectionObserver(
  target,
  (entries) => {
    const entry = entries[0]
    if (entry) {
      isVisible.value = entry.isIntersecting
    }
  },
  { threshold: 0.5 }
)

Lazy loading images

const imageRef = ref<HTMLImageElement>()
const imageSrc = ref('')

useIntersectionObserver(
  imageRef,
  (entries) => {
    if (entries[0]?.isIntersecting) {
      imageSrc.value = imageRef.value?.dataset.src || ''
    }
  },
  { once: true }
)

Infinite scroll

const sentinelRef = ref<HTMLElement>()

useIntersectionObserver(
  sentinelRef,
  (entries) => {
    if (entries[0]?.isIntersecting) {
      loadMoreItems()
    }
  },
  { rootMargin: '100px' }
)

Multiple thresholds

const elementRef = ref<HTMLElement>()

useIntersectionObserver(
  elementRef,
  (entries) => {
    const ratio = entries[0]?.intersectionRatio || 0
    console.log(`${Math.round(ratio * 100)}% visible`)
  },
  { threshold: [0, 0.25, 0.5, 0.75, 1] }
)

Custom scroll container

const scrollContainer = ref<HTMLElement>()
const itemRef = ref<HTMLElement>()

useIntersectionObserver(
  itemRef,
  handleIntersection,
  { root: scrollContainer.value }
)

Convenience Function: useElementIntersection

function useElementIntersection(
  target: MaybeElementRef,
  options?: IntersectionObserverOptions
): UseElementIntersectionReturn
Returns reactive refs for intersection state:
isIntersecting
Readonly<Ref<boolean>>
Whether the element is currently intersecting.
intersectionRatio
Readonly<Ref<number>>
The intersection ratio (0.0 to 1.0).

Example

import { useElementIntersection } from '@vuetify/v0'

const myElement = ref<HTMLElement>()
const { isIntersecting, intersectionRatio } = useElementIntersection(
  myElement,
  { threshold: 0.5 }
)

watchEffect(() => {
  if (isIntersecting.value) {
    console.log('Visible:', intersectionRatio.value)
  }
})

Entry Properties

boundingClientRect
DOMRectReadOnly
The bounding rectangle of the target element.
intersectionRatio
number
The ratio of the intersectionRect to the boundingClientRect.
intersectionRect
DOMRectReadOnly
The rectangle representing the intersection area.
isIntersecting
boolean
Whether the target is intersecting with the root.
rootBounds
DOMRectReadOnly | null
The bounding rectangle of the root element.
target
Element
The target element being observed.
time
number
The time at which the intersection was recorded.

Notes

  • Automatically stops observing when once: true and element intersects
  • Hydration-aware: defers setup until client-side hydration completes
  • SSR-safe: returns valid API with isActive: false when IntersectionObserver is not supported
  • immediate option provides synthetic data on first mount

Build docs developers (and LLMs) love