Element Visibility
useElementVisibility(target, options?) — reactive IntersectionObserver-backed visibility of an element. SSR-safe: observation starts once target resolves to a real element on the client.
function useElementVisibility(
target: MaybeRefOrGetter<HTMLElement | null | undefined>,
options?: UseElementVisibilityOptions,
): UseElementVisibilityReturnEvery call shares one module-level createVisibilityEngine() instance — consistent with the engine's own pooled-observer design.
Options
threshold
MaybeRefOrGetter<number | number[] | undefined> · default: viewportDefaults.threshold (0 unless overridden)
Changing this — as a ref/getter — re-subscribes for real: pool membership is keyed on it, the same as rootMargin/root.
rootMargin
MaybeRefOrGetter<string | undefined> · default: viewportDefaults.rootMargin ('0px' unless overridden)
Same re-subscribe behavior as threshold.
root
MaybeRefOrGetter<HTMLElement | null | undefined> · default: null
Same re-subscribe behavior as threshold/rootMargin.
once
MaybeRefOrGetter<boolean | undefined> · default: viewportDefaults.once (false unless overridden)
Unlike threshold/rootMargin/root, changing this updates the existing subscription live — no re-subscribe, no observer churn.
onEnter
(info: IntersectionInfo) => void · default: —
onLeave
(info: IntersectionInfo) => void · default: —
onEnter/onLeave are deliberately plain values, not MaybeRefOrGetter — a callback closing over a ref's .value already reads it fresh on every call, since Vue composables run once in setup(). There's no staleness for a wrapper to fix here.
threshold/rootMargin/once fall back to the package-wide viewportDefaults object rather than a hardcoded 0/'0px'/false — see setViewportDefaults() below, and Nuxt Module for how it's typically set at the app level.
Return value
isVisible
Ref<boolean>
ratio
Ref<number>
The element's current intersectionRatio.
Example:
<script setup lang="ts">
import { ref } from 'vue'
import { useElementVisibility } from '@macrulez/inview-vue'
const card = ref<HTMLElement | null>(null)
const { isVisible } = useElementVisibility(card, { threshold: 0.5, once: true })
</script>
<template>
<div ref="card" :class="{ 'is-visible': isVisible }">...</div>
</template>Re-subscribes automatically whenever target, threshold, rootMargin, or root change (as a ref/getter); once updates the existing subscription live instead.
threshold/rootMargin/root/once all accept a ref/getter, not just a plain value:
<script setup lang="ts">
const stopOnceUserOptsIn = ref(false)
const { isVisible } = useElementVisibility(card, { once: stopOnceUserOptsIn })
// later: stopOnceUserOptsIn.value = true — updates live, no re-subscribe
</script>Global Defaults
setViewportDefaults() / viewportDefaults — package-wide defaults for threshold/rootMargin/once, overridable once at the app level instead of passing the same options to every call site. Read by this composable, and by v-reveal and <InView> too.
interface ViewportDefaults {
threshold: number | number[]
rootMargin: string
once: boolean
}
const viewportDefaults: ViewportDefaults // { threshold: 0, rootMargin: '0px', once: false }
function setViewportDefaults(overrides: Partial<ViewportDefaults>): voidsetViewportDefaults() only overwrites the fields you actually pass — it merges into the shared viewportDefaults object rather than replacing it. This is exactly what @macrulez/inview-nuxt calls under the hood from its module options.