Viewport Detection
useLazyLoad(options?) is the low-level IntersectionObserver composable useImage() is built on. Exposed directly for anything that needs plain "has this element entered the viewport yet" state without the rest of useImage's loading state machine.
import { useLazyLoad } from '@macrulez/vue-image-kit'
const { isIntersecting, observe } = useLazyLoad({ rootMargin: '300px' })Options
rootMargin
string · default: '200px'. IO rootMargin.
threshold
number · default: 0. IO threshold.
Return value
isIntersecting
Ref<boolean>. true once the observed element has entered the viewport (per rootMargin/threshold). Stays true after the first intersection — the observer disconnects itself once triggered, this is a one-shot "has it appeared yet" signal, not a continuous visibility tracker.
observe
(el: Ref<HTMLElement | null>) => void. Starts watching an element. Safe to call in setup() before the element is mounted — if the ref isn't populated yet, watching is deferred to the next microtask automatically. Calling observe again with a new element tears down the previous observer first.
On the server, isIntersecting starts (and stays) true and observe() is a no-op — this is what makes SSR output render as "already visible" instead of waiting on an API that doesn't exist server-side. Both useImage() and useBackgroundImage() call this composable directly for their own viewport detection. The v-lazy-img directive follows the same SSR contract too — it can't call this composable directly (a directive can't call a composable), but it goes through the same underlying observer pool directly, so it ends up sharing the exact same pooled IntersectionObserver per rootMargin/threshold combination as everything else, not a dedicated observer per element.