autodoload
This commit is contained in:
parent
8cd565c647
commit
62afad71d3
6 changed files with 144 additions and 88 deletions
93
src/hooks/useIntersectionLoad.ts
Normal file
93
src/hooks/useIntersectionLoad.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { RefObject, useEffect, useRef } from 'react'
|
||||
|
||||
interface UseIntersectionLoadOptions extends IntersectionObserverInit {
|
||||
earlyTriggerPx?: number
|
||||
debounceMs?: number
|
||||
}
|
||||
|
||||
export function useIntersectionLoad(
|
||||
callback: () => Promise<void>,
|
||||
elementRef: RefObject<HTMLElement | null>,
|
||||
{
|
||||
earlyTriggerPx = 1800,
|
||||
debounceMs = 300,
|
||||
root = null,
|
||||
threshold = 0.1,
|
||||
rootMargin = '0px',
|
||||
}: UseIntersectionLoadOptions = {},
|
||||
) {
|
||||
const observerRef = useRef<IntersectionObserver | null>(null)
|
||||
const loading = useRef(false)
|
||||
const timeoutRef = useRef<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const el = elementRef.current
|
||||
if (!el) return
|
||||
|
||||
const margin = computeAdjustedRootMargin(rootMargin, earlyTriggerPx)
|
||||
|
||||
const debouncedCallback = () => {
|
||||
if (timeoutRef.current) return
|
||||
timeoutRef.current = setTimeout(async () => {
|
||||
timeoutRef.current = null
|
||||
if (!loading.current) {
|
||||
loading.current = true
|
||||
try {
|
||||
await callback()
|
||||
} finally {
|
||||
loading.current = false
|
||||
if (elementRef.current && observerRef.current) {
|
||||
observerRef.current.unobserve(elementRef.current)
|
||||
observerRef.current.observe(elementRef.current)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, debounceMs)
|
||||
}
|
||||
|
||||
observerRef.current = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const entry = entries[0]
|
||||
if (entry.isIntersecting) {
|
||||
debouncedCallback()
|
||||
}
|
||||
},
|
||||
{
|
||||
root,
|
||||
threshold,
|
||||
rootMargin: margin,
|
||||
},
|
||||
)
|
||||
|
||||
observerRef.current.observe(el)
|
||||
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
if (el && observerRef.current) {
|
||||
observerRef.current.unobserve(el)
|
||||
observerRef.current.disconnect()
|
||||
}
|
||||
}
|
||||
}, [callback, elementRef, root, rootMargin, threshold, earlyTriggerPx, debounceMs])
|
||||
}
|
||||
|
||||
// Utility to adjust rootMargin's top value
|
||||
function computeAdjustedRootMargin(baseMargin: string, extraTopPx: number): string {
|
||||
const parts = baseMargin.split(' ')
|
||||
const top = parts[0] || '0px'
|
||||
const adjustedTop = `${parseInt(top, 10) + extraTopPx}px`
|
||||
|
||||
// Maintain format: top [right bottom left]
|
||||
if (parts.length === 1) {
|
||||
return `${adjustedTop}`
|
||||
} else if (parts.length === 2) {
|
||||
return `${adjustedTop} ${parts[1]}`
|
||||
} else if (parts.length === 3) {
|
||||
return `${adjustedTop} ${parts[1]} ${parts[2]}`
|
||||
} else {
|
||||
return `${adjustedTop} ${parts[1]} ${parts[2]} ${parts[3]}`
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue