SensorBus
Ten uncoordinated pointer, scroll, and resize listeners duplicate velocity mathematics and trigger layout thrashing. SensorBus attaches passive listeners once, computes smoothed derivatives in the conductor input lane before any effect runs, and exposes a zero-allocation live snapshot.
The SensorBus is accessed via the getSensorBus() entry point in vanilla environments or the useSensorBus() hook in React components. Reach for it when multiple elements or shaders continuously react to the same pointer or scroll inputs.
The bus runs in the conductor's input lane, ensuring all sensor positions and velocities (vx, vy, speed) are updated once before any physics or rendering calculations execute. Velocities arrive pre-damped and frame-rate independent to prevent raw pointer jitter.
Adding dozens of consumer effects adds zero event listeners and zero redundant velocity calculations. Each consumer reads the live bus.state object directly during its frame tick without causing React component re-renders.
◆One Bus, Many Readers
Quick start
In React, useSensorBus() retains the singleton bus for the component's lifetime. Read its live properties inside a conductor callback and mutate the DOM directly.
The bus coordinates all input collection; components consume smoothed coordinates directly from the shared state snapshot.
Input Lane Execution & Zero-Allocation State
The SensorBus coordinates with the FrameConductor to compute positions once per frame before any user effect runs.
| Phase | What Happens | Advantage |
|---|---|---|
| 1. Passive Listeners | Hardware events capture raw clientX, clientY, and scroll coordinates | Zero main-thread blocking; browser compositor handles scrolling natively |
| 2. Conductor Input Lane | Bus runs at priority: "essential", computing dt-aware damped vx, vy, and speed | Velocities are smoothed once for the entire page; no jitter from raw deltas |
| 3. Consumer Render Lane | Components read bus.state and write transforms straight to styles or uniforms | Zero React reconciliations, zero GC garbage collection pauses |
API Reference
Syntax
The SensorBus API provides centralized access to pointer, scroll, and viewport measurements with zero per-frame allocation.
Parameters
| Parameters.field | Type | Default | Description |
|---|---|---|---|
| getSensorBus() | SensorBus | Singleton | Returns the shared global instance. Safe to call in SSR environments (listeners only attach on client hydration). |
| bus.retain() | () => () => void | Ref-counted | Increments reference counter and attaches listeners on first caller. Returns an idempotent cleanup function. |
| useSensorBus() | () => SensorBus | React Hook | React hook that automatically retains the bus for the lifetime of the component and releases on unmount. |
Return Value
Live internal object containing pointer, scroll, and viewport structs. Read fields inside frame ticks; do not retain the reference.
Properties & State
| Properties.field | Type | Description |
|---|---|---|
| bus.state | SensorState | The live snapshot containing pointer, scroll, and viewport metrics, refreshed in the input lane every frame. |
PointerSensor (bus.state.pointer)
| PointerSensor (bus.state.pointer).field | Type | Default | Description |
|---|---|---|---|
| x | number | 0 | Current pointer X coordinate in client space (CSS pixels). |
| y | number | 0 | Current pointer Y coordinate in client space (CSS pixels). |
| vx | number | 0 | Damped horizontal velocity in pixels per second. |
| vy | number | 0 | Damped vertical velocity in pixels per second. |
| speed | number | 0 | Magnitude of the velocity vector (hypot(vx, vy)) in pixels per second. |
| down | boolean | false | True if primary pointer button or touch is actively held. |
| seen | boolean | false | Set to true after the first pointer movement is detected; guard against phantom (0,0) jumps on load. |
ScrollSensor (bus.state.scroll)
| ScrollSensor (bus.state.scroll).field | Type | Default | Description |
|---|---|---|---|
| x | number | 0 | Horizontal scroll offset of the window (window.scrollX) in pixels. |
| y | number | 0 | Vertical scroll offset of the window (window.scrollY) in pixels. |
| vx | number | 0 | Damped horizontal scroll velocity in pixels per second. |
| vy | number | 0 | Damped vertical scroll velocity in pixels per second (sign indicates direction). |
ViewportSensor (bus.state.viewport)
| ViewportSensor (bus.state.viewport).field | Type | Default | Description |
|---|---|---|---|
| width | number | 0 | Viewport layout boundary width (window.innerWidth) in pixels. |
| height | number | 0 | Viewport layout boundary height (window.innerHeight) in pixels. |
| dpr | number | 1 | Device pixel ratio for canvas and shader resolution scaling. |
Sensor Lifecycle & Ref Counting
SensorBus uses reference counting to start hardware listeners on the first consumer and detach completely when all consumers release.
Lifecycle & Invariant Guarantees
- →Zero idle overhead: When zero components hold a retain() reference, all window listeners are removed and velocities are zeroed out.
- →Zero memory allocation per frame: The same internal SensorState object is mutated in place every frame, avoiding garbage collection pauses during high-speed animation.
- →Single velocity computation: Pointer and scroll velocity smoothing runs exactly once in the conductor input lane, regardless of how many dozens of elements read it.
- →SSR and hydration safety: Calling getSensorBus() on a Node/SSR server returns a safe mock that never accesses window or throws during prerendering.
- →Compositor-safe passive events: All underlying DOM event listeners are attached with { passive: true } to guarantee touch and scroll performance is never blocked.
◆Production Examples
Battle-tested production patterns ready to copy directly into your codebase.
Hundreds of elements reacting to a single pointer without reading layout in the frame loop.
When NOT to use this
Rules & Gotchas
- →Check pointer.seen before deriving transforms: Until the user interacts with the page, pointer coordinates default to (0,0). Checking seen prevents jarring initial jump calculations.
- →Read in the render or update lane: The bus computes velocities in the input lane, guaranteeing fresh, synchronized metrics by the time render callbacks execute.
- →Never store or mutate bus.state: The state object is a live internal struct updated in place. Read the fields directly during each tick rather than holding references.
- →Pair multiple readers with a single retain: In React, useSensorBus() manages the reference count automatically so unmounting cleans up without leaving orphaned listeners.