useScroll
React Sensor Hook that tracks scroll position and state
useScroll reactively tracks the scroll position and state of a target element (or window/document). It returns a tuple of x position, y position, isScrolling boolean, an arrivedState object ({ top, bottom, left, right } booleans indicating edge arrival), and a directions object indicating the current scroll direction. The hook supports throttling, offset configuration, and callbacks for scroll and scroll-end events.
When to Use
- Implementing scroll-dependent UI such as sticky headers, scroll progress bars, or “back to top” buttons
- Detecting when the user has scrolled to the top or bottom of a container (e.g., for loading more content)
- Tracking scroll direction to show or hide navigation elements
Notes
- SSR-safe: Returns
0for positions,falseforisScrolling, and default arrived/direction states during server-side rendering. No scroll listeners are attached on the server. - Throttling: Use the
throttleoption to limit how often the scroll handler fires, improving performance for complex scroll-based UI updates. - Related hooks: See also
useInfiniteScrollfor automatic load-more triggers,useScrollLockfor disabling scroll, anduseScrollIntoViewfor programmatic scrolling.
Usage
Live Editor
function Demo() { const elementRef = useRef<HTMLDivElement>(null); const [x, y, isScrolling, arrivedState, directions] = useScroll(elementRef); const { left, right, top, bottom } = useMemo( () => arrivedState, [arrivedState], ); const { left: toLeft, right: toRight, top: toTop, bottom: toBottom, } = useMemo(() => directions, [directions]); const absoluteStyle: CSSProperties = { paddingTop: "0.25rem", paddingBottom: "0.25rem", paddingLeft: "0.5rem", paddingRight: "0.5rem", position: "absolute", }; return ( <div style={{ display: "flex" }}> <div ref={elementRef} style={{ width: 300, height: 300, margin: "auto", borderRadius: "0.25rem", overflow: "scroll", }} > <div style={{ width: 500, height: 400, position: "relative" }}> <div style={{ ...absoluteStyle, top: "0rem", left: "0rem", }} > TopLeft </div> <div style={{ ...absoluteStyle, bottom: "0rem", left: "0rem", }} > BottomLeft </div> <div style={{ ...absoluteStyle, top: "0rem", right: "0rem", }} > TopRight </div> <div style={{ ...absoluteStyle, bottom: "0rem", right: "0rem", }} > BottomRight </div> <div style={{ ...absoluteStyle, top: "33.33333%", left: "33.33333%", }} > Scroll Me </div> </div> </div> <div style={{ width: 280, margin: "auto", paddingLeft: "1rem", display: "flex", flexDirection: "column", gap: 5, }} > <div> Position: {x.toFixed(1)}, {y.toFixed(1)} </div> <div>isScrolling: {JSON.stringify(isScrolling)}</div> <div>Top Arrived: {JSON.stringify(top)}</div> <div>Right Arrived: {JSON.stringify(right)}</div> <div>Bottom Arrived: {JSON.stringify(bottom)}</div> <div>Left Arrived: {JSON.stringify(left)}</div> <div>Scrolling Up: {JSON.stringify(toTop)}</div> <div>Scrolling Right: {JSON.stringify(toRight)}</div> <div>Scrolling Down: {JSON.stringify(toBottom)}</div> <div>Scrolling Left: {JSON.stringify(toLeft)}</div> </div> </div> ); };
Result
API
useScroll
Returns
readonly [number, number, boolean, UseScrollArrivedState, UseScrollDirection]: A tuple with the following elements:
- The x value.
- The y value.
- Whether it is scrolling.
- Boundary arrival status.
- Scroll direction.
Arguments
| Argument | Description | Type | DefaultValue |
|---|---|---|---|
| target | dom elment | BasicTarget<Element> | Document | Window (Required) | - |
| options | optional params | UseScrollOptions | undefined | - |
UseScrollOptions
| Property | Description | Type | DefaultValue |
|---|---|---|---|
| throttle | Throttle time for scroll event, it's disabled by default. | number | 0 |
| idle | The check time when scrolling ends.This configuration will be setting to (throttle + idle) when the throttle is configured. | number | - |
| offset | Offset arrived states by x pixels | UseScrollOffset | - |
| onScroll | Trigger it when scrolling. | (e: Event) => void | - |
| onStop | Trigger it when scrolling ends. | (e: Event) => void | - |
| eventListenerOptions | Listener options for scroll event. | boolean | AddEventListenerOptions | {capture: false, passive: true} |
UseScrollArrivedState
| Property | Description | Type | DefaultValue |
|---|---|---|---|
| left | arrived left | boolean (Required) | - |
| right | arrived right | boolean (Required) | - |
| top | arrived top | boolean (Required) | - |
| bottom | arrived bottom | boolean (Required) | - |
UseScrollDirection
| Property | Description | Type | DefaultValue |
|---|---|---|---|
| left | scroll left | boolean (Required) | - |
| right | scroll right | boolean (Required) | - |
| top | scroll top | boolean (Required) | - |
| bottom | scroll bottom | boolean (Required) | - |
BasicTarget
export type BasicTarget<T extends TargetType = Element> = (() => TargetValue<T>) | TargetValue<T> | MutableRefObject<TargetValue<T>>;
TargetValue
type TargetValue<T> = T | undefined | null;
TargetType
type TargetType = HTMLElement | Element | Window | Document | EventTarget;
UseScrollOffset
export interface UseScrollOffset {
left?: number;
right?: number;
top?: number;
bottom?: number;
}