useScrollPositionLoading saved progress…

useScrollPosition

Scroll-driven UI — a progress bar, a shrinking header, a "back to top" button, parallax — needs the current scroll offset in React state. The naive onScroll → setState works but fires dozens of times per second, re-rendering on every pixel and janking the scroll. The fix is two well-known techniques: mark the listener passive (so it can't block scrolling) and throttle with requestAnimationFrame (so you update at most once per frame). useScrollPosition bundles both.

Implement useScrollPosition(). Return { x, y } for window.scrollX/scrollY, seeded from the current position, updated on a passive scroll listener that's throttled to one state update per animation frame, and cleaned up on unmount.

Signature

function useScrollPosition() {
  // returns { x, y }
}

Examples

const { y } = useScrollPosition();
const progress = y / (document.body.scrollHeight - window.innerHeight);
<ProgressBar value={progress} />
const { y } = useScrollPosition();
const showBackToTop = y > 600;

Notes

  • Seed from the current position — initialize state to window.scrollX/Y (0 on the server) so the first render isn't wrong.
  • Passive listener — add { passive: true } so the browser knows you won't preventDefault, keeping scrolling smooth.
  • rAF throttle — many scroll events fire per frame; use a "ticking" flag + requestAnimationFrame so state updates at most once per frame, reading the latest position when the frame runs.
  • Clean up — remove the listener on unmount.
Loading editor…