Files
eranos/src/hooks/useScrollDirection.ts
T
Alex Gleason f74d333d63 Fix mobile bottom nav not hiding on swipe in Vines page
The Vines page uses an internal snap-scroll container instead of window
scroll, so useScrollDirection (which listened to window.scrollY) never
detected any scroll movement. Add a scrollContainer option to
LayoutOptions so pages with custom scroll containers can communicate
the element to MobileBottomNav, which passes it to useScrollDirection.
2026-03-14 01:08:45 -05:00

68 lines
2.1 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
/**
* Tracks the user's scroll direction and returns whether the mobile chrome
* (bottom nav) should be hidden.
*
* Rules:
* - Hidden when the user scrolls DOWN past a small threshold.
* - Revealed when the user scrolls UP by the same threshold.
* - Always revealed when the page is scrolled near the very top.
*
* @param scrollContainer - Optional element whose scroll events should be
* tracked instead of `window`. Useful for pages that scroll an internal
* container (e.g. the Vines snap-scroll feed).
*/
export function useScrollDirection(scrollContainer?: HTMLElement | null): { hidden: boolean } {
const [hidden, setHidden] = useState(false);
const lastScrollY = useRef(0);
const accumulated = useRef(0);
useEffect(() => {
const THRESHOLD = 8; // px of continuous movement before toggling
const NEAR_TOP = 60; // px from top where chrome is always visible
const target: HTMLElement | Window = scrollContainer ?? window;
// Reset state when the scroll target changes
lastScrollY.current = 0;
accumulated.current = 0;
setHidden(false);
const onScroll = () => {
const currentY = scrollContainer ? scrollContainer.scrollTop : window.scrollY;
if (currentY <= NEAR_TOP) {
// Always show chrome near the top of the page
accumulated.current = 0;
setHidden(false);
lastScrollY.current = currentY;
return;
}
const delta = currentY - lastScrollY.current;
lastScrollY.current = currentY;
if (Math.sign(delta) !== Math.sign(accumulated.current)) {
// Direction changed — reset accumulator
accumulated.current = delta;
} else {
accumulated.current += delta;
}
if (accumulated.current > THRESHOLD) {
setHidden(true);
accumulated.current = 0;
} else if (accumulated.current < -THRESHOLD) {
setHidden(false);
accumulated.current = 0;
}
};
target.addEventListener('scroll', onScroll, { passive: true });
return () => target.removeEventListener('scroll', onScroll);
}, [scrollContainer]);
return { hidden };
}