Skip to content

v-intersect

Observe element intersection with the viewport using IntersectionObserver.

Since: 1.1.0

Usage

Basic

vue
<template>
  <div v-intersect="handleIntersect">Observe me</div>
</template>

<script setup>
function handleIntersect(entry, observer) {
  console.log('Intersecting:', entry.isIntersecting)
}
</script>

With Options

vue
<template>
  <div v-intersect="{
    onEnter: handleEnter,
    onLeave: handleLeave,
    threshold: 0.5
  }">
    Track visibility
  </div>
</template>

<script setup>
function handleEnter(entry, observer) {
  console.log('Element entered viewport')
}

function handleLeave(entry, observer) {
  console.log('Element left viewport')
}
</script>

API

Types

typescript
type IntersectHandler = (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void

interface IntersectOptions {
  /** Callback when element intersects */
  handler?: IntersectHandler
  /** Callback when element enters viewport */
  onEnter?: IntersectHandler
  /** Callback when element leaves viewport */
  onLeave?: IntersectHandler
  /** Callback when element changes intersection */
  onChange?: (isIntersecting: boolean, entry: IntersectionObserverEntry) => void
  /** Root element for intersection */
  root?: Element | null
  /** Margin around the root */
  rootMargin?: string
  /** Threshold(s) at which to trigger callback */
  threshold?: number | number[]
  /** Disable the directive */
  disabled?: boolean
  /** Trigger only once */
  once?: boolean
}

type IntersectBinding = IntersectHandler | IntersectOptions

Options

OptionTypeDefaultDescription
handlerFunction-Callback when element intersects
onEnterFunction-Callback when element enters viewport
onLeaveFunction-Callback when element leaves viewport
onChangeFunction-Callback when intersection changes
rootElementnullRoot element for intersection
rootMarginstring'0px'Margin around the root
thresholdnumber | number[]0Threshold(s) to trigger
disabledbooleanfalseDisable the directive
oncebooleanfalseTrigger only once

Examples

Trigger Animation on Scroll

vue
<template>
  <div v-intersect="{
    onEnter: startAnimation,
    once: true
  }" class="animate-on-scroll">
    This will animate once when visible
  </div>
</template>

<script setup>
function startAnimation(entry) {
  entry.target.classList.add('animated')
}
</script>

Lazy Load Component

vue
<template>
  <div v-intersect="{
    onEnter: loadComponent,
    rootMargin: '200px'
  }">
    <component :is="loadedComponent" v-if="loaded" />
    <div v-else>Loading...</div>
  </div>
</template>

<script setup>
import { ref, defineAsyncComponent } from 'vue'

const loaded = ref(false)
const loadedComponent = ref(null)

function loadComponent() {
  loadedComponent.value = defineAsyncComponent(() =>
    import('./HeavyComponent.vue')
  )
  loaded.value = true
}
</script>

Track Visibility Percentage

vue
<template>
  <div v-intersect="{
    threshold: [0, 0.25, 0.5, 0.75, 1],
    onChange: handleVisibilityChange
  }">
    Visibility: {{ visibility }}%
  </div>
</template>

<script setup>
import { ref } from 'vue'

const visibility = ref(0)

function handleVisibilityChange(isIntersecting, entry) {
  visibility.value = Math.round(entry.intersectionRatio * 100)
}
</script>

Composable API

For programmatic use, you can use the useIntersect composable:

typescript
import { useIntersect } from 'directix'

const { isIntersecting, ratio, bind, stop } = useIntersect({
  handler: (entry, observer) => console.log('Intersecting'),
  onEnter: (entry, observer) => console.log('Entered'),
  onLeave: (entry, observer) => console.log('Left'),
  onChange: (isIntersecting, entry) => console.log('Changed'),
  root: null,
  rootMargin: '0px',
  threshold: 0.5,
  once: false
})

// Bind to element
onMounted(() => bind(targetRef.value))

// Stop observing
stop()

UseIntersectOptions

OptionTypeDefaultDescription
handlerIntersectHandler-Callback when element intersects
onEnterIntersectHandler-Callback when element enters viewport
onLeaveIntersectHandler-Callback when element leaves viewport
onChange(isIntersecting: boolean, entry) => void-Callback when intersection changes
rootElement | null | RefnullRoot element for intersection
rootMarginstring'0px'Margin around the root
thresholdnumber | number[]0Threshold(s) to trigger
oncebooleanfalseTrigger only once

UseIntersectReturn

PropertyTypeDescription
isIntersectingReadonly<Ref<boolean>>Whether element is intersecting
ratioReadonly<Ref<number>>Current intersection ratio
bind(element: HTMLElement) => () => voidBind intersection observer
stop() => voidStop observing

Example

vue
<script setup>
import { ref } from 'vue'
import { useIntersect } from 'directix'

const target = ref(null)
const { isIntersecting, bind } = useIntersect({
  threshold: 0.5,
  onEnter: () => console.log('Entered'),
  onLeave: () => console.log('Left')
})

onMounted(() => bind(target.value))
</script>

<template>
  <div ref="target" :class="{ visible: isIntersecting }">
    I'm visible!
  </div>
</template>

Code Generator

Quick Code Generator
<template>
  <div v-intersect="{ threshold: 0.5 }">
    <!-- Observe element intersection with the viewport using IntersectionObserver. directive -->
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

// Configure your options here
const options = { threshold: 0.5 }
</script>

Released under the MIT License.