File
Blob: src/client/hooks/use-scroll-visibility.ts
| 1 | import { useEffect, useState, useRef } from "react"; |
| 2 | |
| 3 | /** |
| 4 | * Tracks whether a header/element should be visible based on scroll direction |
| 5 | * within a given scroll container (not the window). |
| 6 | */ |
| 7 | export function useScrollVisibility(scrollElementId: string, threshold = 5, minOverflow = 80): boolean { |
| 8 | const [visible, setVisible] = useState(true); |
| 9 | const lastScrollY = useRef(0); |
| 10 | |
| 11 | useEffect(() => { |
| 12 | const el = document.getElementById(scrollElementId); |
| 13 | if (!el) return; |
| 14 | |
| 15 | function onScroll() { |
| 16 | const currentY = el!.scrollTop; |
| 17 | if (currentY < 10) { |
| 18 | setVisible(true); |
| 19 | } else if (currentY > lastScrollY.current + threshold) { |
| 20 | // Hiding grows the scroll container by the header height; if overflow |
| 21 | // is shallow, that collapses the scroll and loops visible/hidden. |
| 22 | if (el!.scrollHeight - el!.clientHeight > minOverflow) { |
| 23 | setVisible(false); |
| 24 | } |
| 25 | } |
| 26 | lastScrollY.current = currentY; |
| 27 | } |
| 28 | |
| 29 | el.addEventListener("scroll", onScroll, { passive: true }); |
| 30 | return () => el.removeEventListener("scroll", onScroll); |
| 31 | }, [scrollElementId, threshold, minOverflow]); |
| 32 | |
| 33 | return visible; |
| 34 | } |