Scroll Engine
createScrollEngine(target?, options?) — a framework-agnostic scroll engine for the window or a scrollable element. Batches DOM scroll events into one shared requestAnimationFrame loop and notifies subscribers only when the derived state actually changes.
function createScrollEngine(
target?: Window | HTMLElement,
options?: ScrollEngineOptions,
): ScrollEnginetarget defaults to window on the client. When there's no window (server-side rendering) or no target at all, this returns a no-op engine with a static, zero-value state instead of throwing.
Options
idleTimeout
number · default: 150
How many milliseconds of scroll inactivity before isScrolling flips back to false (and velocity resets to 0).
Return value
getState()
() => ScrollState
Reads the current state synchronously, without subscribing.
subscribe(callback)
(callback: (state: ScrollState) => void) => () => void
Calls callback immediately with the current state, then again on every state change. Returns an unsubscribe function.
scrollTo(position, options?)
(position: number | { x?: number; y?: number }, options?: ScrollToOptions) => void
Scrolls the target. A plain number sets the vertical offset (top). options.behavior defaults to 'smooth', unless prefersReducedMotion() is true, in which case it falls back to 'auto'.
destroy()
() => void
Removes the scroll listener, clears the idle timer, unsubscribes from the shared rAF loop, and clears all subscribers.
ScrollState
The state object passed to subscribe() and returned by getState():
| Field | Type | Notes |
|---|---|---|
x, y | number | Current scroll offset. |
direction | 'up' | 'down' | 'left' | 'right' | null | Derived from the last scroll delta — only changes on a nonzero delta. |
progress | number | 0..1, based on the vertical scroll range if the target is vertically scrollable, otherwise the horizontal range, otherwise 0. |
velocity | number | Pixels per frame, computed from the distance traveled between two rAF ticks. |
isScrolling | boolean | true while scrolling, back to false once idleTimeout ms have passed with no scroll. |
Example:
import { createScrollEngine } from '@macrulez/inview-core'
const scroll = createScrollEngine(window, { idleTimeout: 200 })
const unsubscribe = scroll.subscribe((state) => {
document.title = `${Math.round(state.progress * 100)}% scrolled`
})
// later
scroll.scrollTo({ y: 0 })
unsubscribe()
scroll.destroy()Looking for the reactive version? See Reactive Scroll State for Vue, or React Hooks for React — both wrap this same engine.