@vectorvesper/motionTelemetry · Shared Input Layer

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

1 Passive Listener 1 Velocity Calculation 0 React Re-renders
Demo loads on scroll

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.

PhaseWhat HappensAdvantage
1. Passive ListenersHardware events capture raw clientX, clientY, and scroll coordinatesZero main-thread blocking; browser compositor handles scrolling natively
2. Conductor Input LaneBus runs at priority: "essential", computing dt-aware damped vx, vy, and speedVelocities are smoothed once for the entire page; no jitter from raw deltas
3. Consumer Render LaneComponents read bus.state and write transforms straight to styles or uniformsZero 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.fieldTypeDefaultDescription
getSensorBus()SensorBusSingletonReturns the shared global instance. Safe to call in SSR environments (listeners only attach on client hydration).
bus.retain()() => () => voidRef-countedIncrements reference counter and attaches listeners on first caller. Returns an idempotent cleanup function.
useSensorBus()() => SensorBusReact HookReact hook that automatically retains the bus for the lifetime of the component and releases on unmount.

Return Value

SensorState

Live internal object containing pointer, scroll, and viewport structs. Read fields inside frame ticks; do not retain the reference.

Properties & State

Properties.fieldTypeDescription
bus.stateSensorStateThe live snapshot containing pointer, scroll, and viewport metrics, refreshed in the input lane every frame.

PointerSensor (bus.state.pointer)

PointerSensor (bus.state.pointer).fieldTypeDefaultDescription
xnumber0Current pointer X coordinate in client space (CSS pixels).
ynumber0Current pointer Y coordinate in client space (CSS pixels).
vxnumber0Damped horizontal velocity in pixels per second.
vynumber0Damped vertical velocity in pixels per second.
speednumber0Magnitude of the velocity vector (hypot(vx, vy)) in pixels per second.
downbooleanfalseTrue if primary pointer button or touch is actively held.
seenbooleanfalseSet 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).fieldTypeDefaultDescription
xnumber0Horizontal scroll offset of the window (window.scrollX) in pixels.
ynumber0Vertical scroll offset of the window (window.scrollY) in pixels.
vxnumber0Damped horizontal scroll velocity in pixels per second.
vynumber0Damped vertical scroll velocity in pixels per second (sign indicates direction).

ViewportSensor (bus.state.viewport)

ViewportSensor (bus.state.viewport).fieldTypeDefaultDescription
widthnumber0Viewport layout boundary width (window.innerWidth) in pixels.
heightnumber0Viewport layout boundary height (window.innerHeight) in pixels.
dprnumber1Device 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.

1. Vanilla JS / Canvas SceneManual Retain / Release
2. React ComponentAutomatic Hook Cleanup

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

A single isolated hover or click on a static button.
→ instead Use standard CSS :hover or native onPointerEnter handlers. Routing a single click through a shared bus adds unnecessary indirection.
Storing pointer coordinates in React state to re-render component JSX.
→ instead Mutate DOM style transforms or WebGL uniforms directly. Storing high-frequency pointer data in useState causes 60fps re-renders.
High-frequency stroke capture for digital signature or ink drawing.
→ instead Listen to raw pointermove events or PointerEvent.getCoalescedEvents(). The bus samples once per rAF frame for animation smoothness.
Tracking section visibility for routing or navigation scroll-spy.
→ instead Use native IntersectionObserver, which executes asynchronously off the main animation thread.

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.

Related Motion Components