You are viewing Pre-Alpha documentation.
Vuetify0 Logo

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.

<script setup>
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>

API

ComposableDescription
useResizeObserver→Observe element size changes
useMutationObserver→Observe DOM mutations
useEventListener→General event handling

useIntersectionObserver

  • Type

    interface IntersectionObserverOptions {
      immediate?: boolean
      root?: Element | null
      rootMargin?: string
      threshold?: number | number[]
    }
    
    function useIntersectionObserver(
      target: Ref<Element | undefined>,
      callback: (entries: IntersectionObserverEntry[]) => void,
      options?: IntersectionObserverOptions
    ): {
      isActive: Readonly<Ref<boolean>>
      isIntersecting: Readonly<Ref<boolean>>
      isPaused: Readonly<Ref<boolean>>
      pause: () => void
      resume: () => void
      stop: () => void
    }
  • Details

    Observes when an element intersects with the viewport or a specified ancestor element. Automatically handles cleanup on component unmount.

  • Parameters

    • target: Ref to the element to observe
    • callback: Function called when intersection changes
    • options:
      • immediate: Trigger callback immediately with synthetic entry (default: false)
      • root: The element used as viewport for checking visibility (default: null - uses viewport)
      • rootMargin: Margin around the root, e.g., ‘10px 20px 30px 40px’ (default: ‘0px’)
      • threshold: Visibility percentage(s) that trigger the callback, 0-1 or array (default: 0)
  • Returns

    • isActive: Whether the observer is currently created and observing
    • isIntersecting: Whether the element is currently intersecting
    • isPaused: Whether observation is paused
    • pause(): Pause observation
    • resume(): Resume observation
    • stop(): Stop observation permanently
  • Example

    const element = useTemplateRef('element')
    
    const { isIntersecting, pause, resume } = useIntersectionObserver(
      element,
      ([entry]) => {
        console.log('Intersection ratio:', entry.intersectionRatio)
      },
      {
        threshold: [0, 0.5, 1], // Trigger at 0%, 50%, and 100%
        rootMargin: '100px' // Start observing 100px before entering
      }
    )

useElementIntersection

  • Type

    function useElementIntersection(
      target: Ref<Element | undefined>,
      options?: IntersectionObserverOptions
    ): {
      isActive: Readonly<Ref<boolean>>
      isIntersecting: Readonly<Ref<boolean>>
      intersectionRatio: Readonly<Ref<number>>
      isPaused: Readonly<Ref<boolean>>
      pause: () => void
      resume: () => void
      stop: () => void
    }
  • Details

    Convenience function for tracking element intersection state reactively without a callback.

  • Returns

    • isActive: Whether the observer is currently created and observing
    • isIntersecting: Whether the element is currently intersecting
    • intersectionRatio: How much of the element is visible (0-1)
    • isPaused: Whether observation is paused
    • pause(): Pause observation
    • resume(): Resume observation
    • stop(): Stop observation permanently
  • Example

    const image = useTemplateRef('image')
    const { isIntersecting } = useElementIntersection(image, {
      threshold: 0.1
    })
    
    // Use isIntersecting in template or watch
    watch(isIntersecting, (visible) => {
      if (visible) loadImage()
    })

Lifecycle & Cleanup

Automatic Cleanup

useIntersectionObserver automatically disconnects the observer when:

  • The component unmounts
  • The Vue effect scope is disposed
  • You call the returned stop() function

Implementation:

// Uses Vue's onScopeDispose internally
onScopeDispose(() => observer.disconnect())

This prevents memory leaks by ensuring observers don’t continue running after the component is destroyed.

Manual Control

The composable returns control functions for fine-grained lifecycle management:

const { isActive, isIntersecting, pause, resume, stop } = useIntersectionObserver(
  element,
  callback
)

// Check if observer is active
console.log(isActive.value) // true

// Temporarily pause observation (keeps observer alive)
pause()
console.log(isActive.value) // false
console.log(isIntersecting.value) // false (reset on pause)

// Resume observation
resume()
console.log(isActive.value) // true

// Permanently stop and disconnect observer
stop()
console.log(isActive.value) // false

State properties:

  • isActive: True when the observer exists and is observing (false when paused or stopped)
  • isPaused: True when observation is temporarily paused
  • isIntersecting: True when the element is currently intersecting the viewport

Difference between pause and stop:

  • pause(): Temporarily stops observing, can be resumed with resume()
  • stop(): Permanently disconnects the observer, cannot be restarted

Reactive Target

The target element can be reactive. When the target ref changes, the observer automatically re-attaches:

const element = ref<HTMLElement | null>(null)

useIntersectionObserver(element, callback)

// Later - observer automatically reconnects to new element
element.value = document.querySelector('.new-target')

Template Refs

Works seamlessly with Vue’s template refs:

<script setup>
import { useTemplateRef } from 'vue'
import { useIntersectionObserver } from '@vuetify/v0'

const section = useTemplateRef('section')

const { isIntersecting } = useIntersectionObserver(
  section,
  ([entry]) => {
    console.log('Section visibility:', entry.isIntersecting)
  }
)
</script>

<template>
  <section ref="section">
    <p v-if="isIntersecting">Now visible!</p>
  </section>
</template>

Usage Outside Components

If called outside a component setup function:

  • No automatic cleanup (no active effect scope)
  • Must manually call stop() to prevent memory leaks
  • Consider wrapping in effectScope():
import { effectScope } from 'vue'

const scope = effectScope()

scope.run(() => {
  useIntersectionObserver(element, callback)
})

// Later, cleanup all observers in the scope
scope.stop()

SSR Considerations

IntersectionObserver is a browser-only API. The composable checks for browser environment internally:

// Safe to call during SSR - will not throw
const { isIntersecting } = useIntersectionObserver(element, callback)
// isIntersecting.value will be false in SSR

Performance Tips

Use appropriate thresholds:

// Trigger once when element appears
useIntersectionObserver(element, callback, { threshold: 0 })

// Trigger at multiple visibility levels
useIntersectionObserver(element, callback, { threshold: [0, 0.25, 0.5, 0.75, 1] })

Use rootMargin for early loading:

// Start loading 200px before element enters viewport
useIntersectionObserver(element, callback, {
  rootMargin: '200px'
})

Pause when not needed:

const { pause, resume } = useIntersectionObserver(element, callback)

// Pause during heavy operations
pause()
performHeavyWork()
resume()

© 2016-2025 Vuetify, LLC