useRafFn
Execute a function on every animation frame (requestAnimationFrame) with reactive isActive state and pause/resume control.
The callback receives delta (ms since last frame) and timestamp (ms since page load).
import { import useRafFn
useRafFn } from "@usels/web";
const { const isActive: any
isActive, const pause: any
pause, const resume: any
resume } = import useRafFn
useRafFn(({ delta: any
delta, timestamp: any
timestamp }) => { // called ~60 times/sec any
console.any
log(`delta: ${delta: any
delta}ms`);});FPS limit
Section titled “FPS limit”import { import useRafFn
useRafFn } from "@usels/web";import { function observable<T>(): Observable<T | undefined> (+2 overloads)
observable } from "@legendapp/state";
const const fps$: any
fps$ = observable<unknown>(value: Promise<unknown> | (() => unknown) | unknown): any (+2 overloads)
observable(30);import useRafFn
useRafFn(({ delta: any
delta }) => {}, { fpsLimit: any
fpsLimit: const fps$: any
fps$ });// caps execution to 30fps; fps$.set(60) applies next frameRun once
Section titled “Run once”import { import useRafFn
useRafFn } from "@usels/web";
import useRafFn
useRafFn( ({ timestamp: any
timestamp }) => { any
console.any
log("ran once at", timestamp: any
timestamp); }, { once: boolean
once: true });Note:
once: truemeans “pause after the first frame per resume cycle”. Callingresume()again starts a new cycle.
delta on the first frame
Section titled “delta on the first frame”delta is always 0 on the first frame after resume(). This avoids a production pitfall where the DOMHighResTimeStamp accumulates from page load (potentially thousands of ms), causing physics simulations to jump on the first frame.
fpsLimit valid values
Section titled “fpsLimit valid values”Only positive finite numbers limit the frame rate. All other values behave as unlimited:
fpsLimit | Behavior |
|---|---|
null (default) | Unlimited |
60 | Capped at 60 fps |
0 | Unlimited (not “stopped”) |
NaN | Unlimited |
Infinity | Unlimited |
| negative | Unlimited |
To stop the loop, use pause() instead of setting fpsLimit to a non-positive value.
Reactive fpsLimit
Section titled “Reactive fpsLimit”Pass an Observable when you need to change the fps cap at runtime. A plain number is captured at mount time and will not update if the component re-renders with a new value (stale closure):
import { import useRafFn
useRafFn } from "@usels/web";import { function observable<T>(): Observable<T | undefined> (+2 overloads)
observable } from "@legendapp/state";
// ✅ Reactive — changes apply on the next frameconst const fps$: any
fps$ = observable<unknown>(value: Promise<unknown> | (() => unknown) | unknown): any (+2 overloads)
observable(30);import useRafFn
useRafFn(any
fn, { fpsLimit: any
fpsLimit: const fps$: any
fps$ });const fps$: any
fps$.any
set(60); // takes effect immediately
// ❌ Stale closure — re-render with a new number has no effectconst [const fps: any
fps, const setFps: any
setFps] = any
useState(30);import useRafFn
useRafFn(any
fn, { fpsLimit: any
fpsLimit: const fps: any
fps }); // captures 30 at mount, never updatesType Declarations
Section titled “Type Declarations”export interface UseRafFnCallbackArguments { delta: number; timestamp: DOMHighResTimeStamp;}export interface UseRafFnOptions { immediate?: boolean; fpsLimit?: MaybeObservable<number | null>; once?: boolean;}export declare function useRafFn(fn: (args: UseRafFnCallbackArguments) => void, options?: UseRafFnOptions): Pausable;Source
Section titled “Source”Contributors
Section titled “Contributors”- tigerwest
Changelog
Section titled “Changelog”a7392ab2026-03-06 - feat(core,browser): add sync strategy hooks (tigerwest)