Files
AOUN/src/hooks/use-scrolling.ts
2025-11-28 22:44:54 +08:00

76 lines
2.1 KiB
TypeScript

import type { RefObject } from "react"
import { useEffect, useState } from "react"
type ScrollTarget = RefObject<HTMLElement> | Window | null | undefined
type EventTargetWithScroll = Window | HTMLElement | Document
interface UseScrollingOptions {
debounce?: number
fallbackToDocument?: boolean
}
export function useScrolling(
target?: ScrollTarget,
options: UseScrollingOptions = {}
): boolean {
const { debounce = 150, fallbackToDocument = true } = options
const [isScrolling, setIsScrolling] = useState(false)
useEffect(() => {
// Resolve element or window
const element: EventTargetWithScroll =
target && typeof Window !== "undefined" && target instanceof Window
? target
: ((target as RefObject<HTMLElement>)?.current ?? window)
// Mobile: fallback to document when using window
const eventTarget: EventTargetWithScroll =
fallbackToDocument &&
element === window &&
typeof document !== "undefined"
? document
: element
const on = (
el: EventTargetWithScroll,
event: string,
handler: EventListener
) => el.addEventListener(event, handler, true)
const off = (
el: EventTargetWithScroll,
event: string,
handler: EventListener
) => el.removeEventListener(event, handler)
let timeout: ReturnType<typeof setTimeout>
const supportsScrollEnd = element === window && "onscrollend" in window
const handleScroll: EventListener = () => {
if (!isScrolling) setIsScrolling(true)
if (!supportsScrollEnd) {
clearTimeout(timeout)
timeout = setTimeout(() => setIsScrolling(false), debounce)
}
}
const handleScrollEnd: EventListener = () => setIsScrolling(false)
on(eventTarget, "scroll", handleScroll)
if (supportsScrollEnd) {
on(eventTarget, "scrollend", handleScrollEnd)
}
return () => {
off(eventTarget, "scroll", handleScroll)
if (supportsScrollEnd) {
off(eventTarget, "scrollend", handleScrollEnd)
}
clearTimeout(timeout)
}
}, [target, debounce, fallbackToDocument, isScrolling])
return isScrolling
}