Skip to content
Timer

useRafFn

Call a function on every requestAnimationFrame with pause/resume control

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).

requestAnimationFrame
Paused

Tracks frame count and delta time.

Frames
0
Delta
0ms
import {
import useRafFn
useRafFn
} from "@usels/web";
function
function Component(): void
Component
() {
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`);
});
}
import { observable, useRafFn } from "@usels/web";
function Component() {
const fps$ = observable(30);
useRafFn(({ delta }) => {}, { fpsLimit: fps$ });
// caps execution to 30fps; fps$.set(60) applies next frame
}
import { useRafFn } from "@usels/web";
function Component() {
useRafFn(
({ timestamp }) => {
console.log("ran once at", timestamp);
},
{ once: true }
);
}

Note: once: true means “pause after the first frame per resume cycle”. Calling resume() again starts a new cycle.

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.

Only positive finite numbers limit the frame rate. All other values behave as unlimited:

fpsLimitBehavior
null (default)Unlimited
60Capped at 60 fps
0Unlimited (not “stopped”)
NaNUnlimited
InfinityUnlimited
negativeUnlimited

To stop the loop, use pause() instead of setting fpsLimit to a non-positive value.

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):

// @noErrors
import { observable, useRafFn } from "@usels/web";
// ✅ Reactive — changes apply on the next frame
const fps$ = observable(30);
useRafFn(fn, { fpsLimit: fps$ });
fps$.set(60); // takes effect immediately
// ❌ Stale closure — re-render with a new number has no effect
const [fps, setFps] = useState(30);
useRafFn(fn, { fpsLimit: fps }); // captures 30 at mount, never updates
ParameterTypeDescription
fn(args: RafFnCallbackArguments) => void-
optionsRafFnOptions | undefined (optional)-
OptionTypeDefaultDescription
immediatebooleantrueIf true, starts immediately on creation.
fpsLimitMaybeObservable<number | null>nullCap execution to this many frames per second. null = unlimited.
oncebooleanfalseIf true, pauses after the first frame executes.

Pausable

NameTypeDescription
isActive$ReadonlyObservable<boolean>-
pauseFn-
resumeFn-

View on GitHub