Skip to content
Sensors

useScroll

Tracks the scroll position, scroll direction, arrived state (top/bottom/left/right), and scrolling status of any scrollable target — HTMLElement, Document, or Window — as reactive Observable values.

x: 0y: 0idletopbottom
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
Item 11
Item 12
Item 13
Item 14
Item 15
Item 16
Item 17
Item 18
Item 19
Item 20

Scroll inside the box to see x, y, arrivedState, and directions update.

import {
const useScroll: (element: MaybeEventTarget<Element | Document | Window>, options?: DeepMaybeObservable<UseScrollOptions>) => UseScrollReturn
useScroll
} from "@usels/web";
import {
const useRef$: {
<T = any>(initialValue: null): Ref$<T | null>;
<T = any>(initialValue: T): Ref$<T>;
<T = any>(): Ref$<T | null>;
}
useRef$
} from "@usels/core";
function
function Component(): JSX.Element
Component
() {
const
const el$: Ref$<HTMLDivElement | null>
el$
=
useRef$<HTMLDivElement>(): Ref$<HTMLDivElement | null> (+2 overloads)

Core (framework-agnostic) version of useRef$. Creates an observable element ref with opaque wrapping.

  • non-null value → Ref$<T>: current, get(), peek() return T
  • null / no arg → Ref$<T | null>: current, get(), peek() return T | null

Nullability is expressed via the type parameter, mirroring T | null at the call site.

useRef$
<
interface HTMLDivElement
HTMLDivElement
>();
const {
const x$: any
x$
,
const y$: any
y$
,
const arrivedState$: any
arrivedState$
} =
function useScroll(element: MaybeEventTarget<Element | Document | Window>, options?: DeepMaybeObservable<UseScrollOptions>): UseScrollReturn

Framework-agnostic reactive scroll tracker. Monitors scroll events on the target (Element, Document, Window, Ref$, or Observable) and exposes reactive position, direction, arrived-state, and scrolling status.

useScroll
(
const el$: Ref$<HTMLDivElement | null>
el$
);
return (
<
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
RefAttributes<HTMLDivElement>.ref?: Ref<HTMLDivElement> | undefined

Allows getting a ref to the component instance. Once the component unmounts, React will set ref.current to null (or call the ref with null if you passed a callback ref).

ref
={
const el$: Ref$<HTMLDivElement | null>
el$
}
HTMLAttributes<HTMLDivElement>.style?: CSSProperties | undefined
style
={{
StandardShorthandProperties<string | number, string & {}>.overflow?: Property.Overflow | undefined

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

Syntax: [ visible | hidden | clip | scroll | auto ]{1,2}

Initial value: visible

| Chrome | Firefox | Safari | Edge | IE | | :----: | :-----: | :----: | :----: | :---: | | 1 | 1 | 1 | 12 | 4 |

overflow
: "auto",
StandardLonghandProperties<string | number, string & {}>.height?: Property.Height<string | number> | undefined

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

Syntax: auto | <length-percentage [0,∞]> | min-content | max-content | fit-content | fit-content(<length-percentage [0,∞]>) | <calc-size()> | <anchor-size()>

Initial value: auto

| Chrome | Firefox | Safari | Edge | IE | | :----: | :-----: | :----: | :----: | :---: | | 1 | 1 | 1 | 12 | 4 |

height
: 300 }}>
<
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>
scrollX: {
const x$: any
x$
.
any
get
()}, scrollY: {
const y$: any
y$
.
any
get
()}
{
const arrivedState$: any
arrivedState$
.
any
bottom
.
any
get
() && " — reached bottom"}
</
JSX.IntrinsicElements.p: DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
p
>
</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
);
}

Use useWindowScroll for the common case, or pass window directly.

import { useScroll } from "@usels/web";
function Component() {
const { y$, arrivedState$, isScrolling$ } = useScroll(window);
}

Use offset to declare a threshold (in px) before the edge is considered “arrived”.

const { arrivedState$ } = useScroll(el$, {
offset: { bottom: 100 }, // bottom=true when within 100px of the end
});
const { isScrolling$ } = useScroll(el$, {
idle: 300, // ms to wait before isScrolling becomes false (default: 200)
onStop: () => {
// called when scrolling stops
},
});
const { x$, y$ } = useScroll(el$, { throttle: 50 }); // handler fires at most once per 50ms
const { y$, measure } = useScroll(el$);
// Call measure() to force-sync scroll state without a scroll event
measure();

Options can be passed as plain values, per-field Observables, or a single Observable<UseScrollOptions>. Changes are picked up reactively.

import { observable } from "@usels/core";
const idle$ = observable(200);
const { isScrolling$ } = useScroll(el$, { idle: idle$ });
// Later: update idle time reactively
idle$.set(500);

Reactive observables, not state. All returned values (x$, y$, isScrolling$, arrivedState$, directions$) are Legend-State Observables. Read them with .get() inside a reactive context to avoid unnecessary re-renders.

measure() is synchronous. It immediately reads the current scroll values from the DOM and updates all observables. Useful after programmatic scroll operations.

arrivedState initial values. On mount, top and left default to true, and bottom/right default to false. After the first measure() call (triggered automatically on mount), all values are synced with actual DOM state.

options is DeepMaybeObservable. Each option field can be a plain value or an Observable. Callback options (onScroll, onStop, onError) are passed as plain functions.

ParameterTypeDescription
elementMaybeEventTarget<Element | Document | Window>-
optionsUseScrollOptions (optional)-
OptionTypeDefaultDescription
throttlenumber--
idlenumber--
onScroll((e: Event) => void)--
onStop(() => void)--
onError((error: unknown) => void)--
offset{ left?: number | undefined; right?: number | undefined; top?: number | undefined; bottom?: number | undefined; }--
behaviorScrollBehavior--
eventListenerOptionsAddEventListenerOptions--
windowWindowSource--

UseScrollReturn

NameTypeDescription
x$ObservablePrimitive<number>-
y$ObservablePrimitive<number>-
isScrolling$ObservableBoolean<boolean>-
arrivedState$Observable<ArrivedState>-
directions$Observable<ScrollDirections>-
measure() => void-

View on GitHub