Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

useIntersectionObserver

A composable for detecting when elements enter or leave the viewport using the Intersection Observer API with automatic cleanup.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

StableAdvancedJun 29, 2026

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.

Tip

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.

vue
<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:

Intersection Observer Hierarchy

Use controls to zoom and pan. Click outside or press Escape to close.

Intersection Observer Hierarchy

Options

OptionTypeDefaultNotes
immediatebooleanfalseFire the callback immediately on mount before any intersection
oncebooleanfalseStop observing after the first intersection fires
thresholdnumber | number[]0Intersection ratio(s) at which the callback fires
rootElement | nullnullAncestor to use as viewport (null = browser viewport)
rootMarginstring'0px'CSS margin around the root for intersection calculations

Reactivity

Property/MethodReactiveNotes
isActiveComputed from observer ref
isIntersectingShallowRef, readonly
isPausedShallowRef, readonly
targetAccepts MaybeRef, watched for changes
pause()Temporarily stop observing without disconnecting
resume()Resume after pause()
stop()Disconnect the observer permanently

useElementIntersection

Property/MethodReactiveNotes
isIntersectingShallowRef, readonly
intersectionRatioShallowRef, readonly (0.0 to 1.0)

Examples

Scroll Reveal with Visibility Percentage

Six cards that fade in and slide up with a staggered CSS delay as they scroll into view inside a fixed-height scroll container, each card showing a live visibility percentage progress bar.

The example creates one useIntersectionObserver instance per card inside onMounted, passing the scoped root element as the IntersectionObserver root so intersection is measured relative to the container rather than the browser viewport. A 21-stop threshold array (Array.from({ length: 21 }, (_, i) => i / 20)) fires the callback at every 5% increment, which is how ratios stays continuously updated for the smooth progress bar animation. Cards are permanently revealed — added to a Set — once they cross 30% visibility; the Set is stored in a shallowRef and replaced with a new Set on each update to trigger Vue’s shallow-change detection.

Staggered entrance is achieved entirely in CSS with transitionDelay: ${index * 75}ms — no JavaScript timers. The composable does the detection work; CSS handles the choreography. Reach for this pattern for entrance animations, ad viewability tracking, or any scroll-triggered effect that needs per-element granularity. For a simpler “has it ever been visible” gate, combine with { once: true } to disconnect the observer after first intersection and avoid ongoing callback overhead — see useImage for an example of that pattern.

Scroll to reveal cards0/6 revealed
Scroll down to see the magic

Lazy Loading

Load images only when entering viewport

0%

Infinite Scroll

Fetch more content as users scroll

0%

Analytics

Track visibility for engagement metrics

0%

Animations

Trigger entrance animations on scroll

0%

Video Playback

Auto-play videos when visible

0%

Ad Viewability

Measure ad impressions accurately

0%
You've reached the end

FAQ

Discord
Need help? Join our community for support and discussions ↗

API Reference

The following API details are for the useIntersectionObserver composable.
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/