useWindowScroll
EditTracks the window scroll position and exposes a typed scroll control.
import { useCallback, useMemo, useSyncExternalStore } from "react";
export interface WindowScrollPosition { x: number; y: number;}
export interface UseWindowScrollReturn extends WindowScrollPosition { scrollTo: { (options: ScrollToOptions): void; (x: number, y: number): void; };}
const serverScrollPosition: WindowScrollPosition = { x: 0, y: 0 };let cachedScrollPosition: WindowScrollPosition = serverScrollPosition;
const getScrollPosition = (): WindowScrollPosition => { if (typeof window === "undefined") return serverScrollPosition; const x = window.scrollX; const y = window.scrollY; if (cachedScrollPosition.x !== x || cachedScrollPosition.y !== y) { cachedScrollPosition = { x, y }; } return cachedScrollPosition;};
const subscribeToScroll = (notify: () => void): (() => void) => { if (typeof window === "undefined") return () => {}; window.addEventListener("scroll", notify, { passive: true }); return () => window.removeEventListener("scroll", notify);};
/** Returns the current window scroll position and a typed scrollTo control. */export const useWindowScroll = (): UseWindowScrollReturn => { const position = useSyncExternalStore( subscribeToScroll, getScrollPosition, () => serverScrollPosition, ); const scrollTo = useCallback( ((first: number | ScrollToOptions, second?: number) => { if (typeof window === "undefined") return; if (typeof first === "number") window.scrollTo(first, second ?? 0); else window.scrollTo(first); }) as UseWindowScrollReturn["scrollTo"], [], ); return useMemo(() => ({ ...position, scrollTo }), [position, scrollTo]);};Download
Section titled “Download”wget -O src/hooks/useWindowScroll.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/react-hooks/useWindowScroll.tstemp_dir="$(mktemp -d)" && bunx degit jrTilak/lazykit/registry/react-hooks "$temp_dir" && mv "$temp_dir/useWindowScroll.ts" src/hooks/useWindowScroll.ts && rm -r "$temp_dir"Examples
Section titled “Examples”import { useWindowScroll } from "./useWindowScroll";
export const WindowScrollExample = () => { const { x, y, scrollTo } = useWindowScroll(); return ( <p> Scroll: {Math.round(x)}, {Math.round(y)}{" "} <button type="button" onClick={() => scrollTo({ top: 0, behavior: "smooth" })}> Back to top </button> </p> );};This hook takes no arguments.
Returns
Section titled “Returns”UseWindowScrollReturn with x, y, and both native scrollTo signatures.