useIntersectionObserver
A composable for detecting when elements enter or leave the viewport using the Intersection Observer API with automatic cleanup.
Usage
The useIntersectionObserver composable wraps the Intersection Observer API to detect when elements become visible in the viewport. It’s useful for lazy loading images, infinite scroll, entrance animations, and performance optimizations.
Why wrap IntersectionObserver? The native IntersectionObserver has no awareness of Vue’s effectScope lifecycle. If you create one inside a composable, it won’t automatically disconnect when the scope is disposed. useIntersectionObserver integrates onScopeDispose for automatic cleanup, defers creation until after hydration for SSR safety, and adds reactive target tracking — things the native API can’t do on its own.
<script setup lang="ts">
import { useIntersectionObserver } from '@vuetify/v0'
import { ref, useTemplateRef } from 'vue'
const target = useTemplateRef('target')
const isVisible = ref(false)
useIntersectionObserver(target, (entries) => {
isVisible.value = entries[0].isIntersecting
}, {
threshold: 0.5, // Trigger when 50% visible
rootMargin: '0px'
})
</script>
<template>
<div>
<div style="height: 100vh">Scroll down to see the element</div>
<div ref="target" :class="{ visible: isVisible }">
I'm {{ isVisible ? 'visible' : 'hidden' }}
</div>
</div>
</template>Architecture
useIntersectionObserver wraps the native IntersectionObserver API with Vue reactivity:
Options
| Option | Type | Default | Notes |
|---|---|---|---|
immediate | boolean | false | Fire the callback immediately on mount before any intersection |
once | boolean | false | Stop observing after the first intersection fires |
threshold | number | number[] | 0 | Intersection ratio(s) at which the callback fires |
root | Element | null | null | Ancestor to use as viewport (null = browser viewport) |
rootMargin | string | '0px' | CSS margin around the root for intersection calculations |
Reactivity
| Property/Method | Reactive | Notes |
|---|---|---|
isActive | Computed from observer ref | |
isIntersecting | ShallowRef, readonly | |
isPaused | ShallowRef, readonly | |
target | Accepts MaybeRef, watched for changes | |
pause() | — | Temporarily stop observing without disconnecting |
resume() | — | Resume after pause() |
stop() | — | Disconnect the observer permanently |
useElementIntersection
| Property/Method | Reactive | Notes |
|---|---|---|
isIntersecting | ShallowRef, readonly | |
intersectionRatio | ShallowRef, readonly (0.0 to 1.0) |
Examples
Scroll Reveal with Visibility Percentage
Cards that fade in and slide up with a staggered delay as they scroll into view, with a progress bar showing each card’s visibility percentage.
Lazy Loading
Load images only when entering viewport
Infinite Scroll
Fetch more content as users scroll
Analytics
Track visibility for engagement metrics
Animations
Trigger entrance animations on scroll
Video Playback
Auto-play videos when visible
Ad Viewability
Measure ad impressions accurately
Functions
useIntersectionObserver
(target: MaybeElementRef, callback: (entries: IntersectionObserverEntry[]) => void, options?: IntersectionObserverOptions) => UseIntersectionObserverReturnA composable that uses the Intersection Observer API to detect when an element is visible in the viewport.
useElementIntersection
(target: MaybeElementRef, options?: IntersectionObserverOptions) => UseElementIntersectionReturnA convenience composable that uses the Intersection Observer API to detect when an element is visible in the viewport.
Options
immediate
boolean | undefinedonce
boolean | undefinedroot
Element | null | undefinedrootMargin
string | undefinedthreshold
number | number[] | undefinedProperties
isActive
Readonly<Ref<boolean, boolean>>Whether the observer is currently active (created and observing)
isIntersecting
Readonly<Ref<boolean, boolean>>Whether the target element is currently intersecting with the viewport