IntersectionObserver, bukan menghitung posisi saat scroll. Bedanya bukan gaya: menghitung posisi berarti bekerja di setiap peristiwa scroll, di utas utama, untuk setiap elemen yang dipantau. IntersectionObserver dikerjakan peramban di luar itu dan cuma memanggil balik saat keadaannya benar-benar berubah.
Ini yang dipakai untuk memuat gambar saat mendekat, menandai bagian aktif di daftar isi, dan memuat halaman berikutnya saat dasar daftar tersentuh.
Installation
pnpm dlx shadcn-vue@latest add @kliping/use-in-viewport Perlu @kliping terdaftar di components.json project Anda. Lihat cara menyiapkannya.
Ikut terpasang sendiri: target.
Salin dulu target — masing-masing memuat sumbernya di halamannya sendiri — lalu tempel berkas ini.
ts
import { getCurrentScope, onScopeDispose, ref, watch } from 'vue'
import { getTargetElement } from '@/lib/target'
import type { BasicTarget } from '@/lib/target'
export interface UseInViewportOptions {
rootMargin?: string
threshold?: number | number[]
/** Element treated as the "screen". Leave empty for the viewport. */
root?: BasicTarget
callback?: (entry: IntersectionObserverEntry) => void
}
export function useInViewport(
target: BasicTarget | BasicTarget[],
options: UseInViewportOptions = {},
) {
const { rootMargin, threshold, root, callback } = options
const inViewport = ref<boolean | undefined>(undefined)
const ratio = ref<number | undefined>(undefined)
let observer: IntersectionObserver | undefined
const teardown = () => {
observer?.disconnect()
observer = undefined
}
const stopWatch = watch(
() => {
const targets = Array.isArray(target) ? target : [target]
return targets.map((t) => getTargetElement(t))
},
(elements) => {
teardown()
const present = elements.filter(Boolean) as Element[]
if (!present.length || typeof IntersectionObserver === 'undefined') return
observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
ratio.value = entry.intersectionRatio
inViewport.value = entry.isIntersecting
callback?.(entry)
}
},
{
rootMargin,
threshold,
root: (getTargetElement(root) as Element | null) ?? null,
},
)
for (const el of present) observer.observe(el)
},
{ immediate: true, flush: 'post' },
)
const stop = () => {
teardown()
stopWatch()
}
if (getCurrentScope()) onScopeDispose(stop)
return { inViewport, ratio, stop: stop }
}API
ts
export interface UseInViewportOptions {
rootMargin?: string
threshold?: number | number[]
/** Element treated as the "screen". Leave empty for the viewport. */
root?: BasicTarget
callback?: (entry: IntersectionObserverEntry) => void
}
export function useInViewport(
target: BasicTarget | BasicTarget[],
options: UseInViewportOptions = {},
)