You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

75 lines
2.1 KiB

import { elementIsNearVisibleScrollport, nearestScrollportRoot } from '@/lib/utils'
import { useEffect, useLayoutEffect, useState, type RefObject } from 'react'
/** Pixels beyond the viewport edge to treat as visible (prefetch before scroll lands). */
export const NEAR_VIEWPORT_MARGIN_PX = 320
/** @deprecated Prefer {@link elementIsNearVisibleScrollport} — respects nested scrollports. */
export function elementIsNearViewport(el: HTMLElement, marginPx: number): boolean {
return elementIsNearVisibleScrollport(el, marginPx)
}
/**
* True when `ref` points at an element intersecting the viewport or nearest scrollport (with margin).
* When `enabled` is false, returns true immediately (no deferral).
*/
export function useNearViewport(
ref: RefObject<HTMLElement | null>,
options?: { enabled?: boolean; marginPx?: number }
): boolean {
const enabled = options?.enabled !== false
const marginPx = options?.marginPx ?? NEAR_VIEWPORT_MARGIN_PX
const [isNear, setIsNear] = useState(() => !enabled)
useLayoutEffect(() => {
if (!enabled) {
setIsNear(true)
return
}
const el = ref.current
if (!el) {
setIsNear(false)
return
}
if (elementIsNearVisibleScrollport(el, marginPx)) {
setIsNear(true)
return
}
setIsNear(false)
}, [enabled, marginPx, ref])
useEffect(() => {
if (!enabled || isNear) return
if (typeof IntersectionObserver === 'undefined') {
setIsNear(true)
return
}
let io: IntersectionObserver | null = null
let raf = 0
const attach = () => {
const el = ref.current
if (!el || io) return
const scrollRoot = nearestScrollportRoot(el)
io = new IntersectionObserver(
(entries) => {
if (entries.some((e) => e.isIntersecting)) {
setIsNear(true)
}
},
{
root: scrollRoot ?? null,
rootMargin: `${marginPx}px`,
threshold: 0.01
}
)
io.observe(el)
}
raf = requestAnimationFrame(attach)
return () => {
cancelAnimationFrame(raf)
io?.disconnect()
}
}, [enabled, isNear, marginPx, ref])
return isNear
}