Skip to main content
Solid Primitives 2

Primitive that facilitates RAF functionality

StageCategoryVersionLast UpdatedDemo
3Animation4.0.0-next.6 (next)Nov 12, 2026Demo →
Terminal window
npm i @solid-primitives/raf@next

Reactive primitives providing support to window.requestAnimationFrame.

createRAF

A primitive creating reactive window.requestAnimationFrame, that is automatically disposed onCleanup.

Loading playground…

It takes a callback argument that will run on every frame.

Returns a signal if currently running as well as start and stop methods

import createRAF from "@solid-primitives/raf";
const [running, start, stop] = createRAF(timeStamp => console.log("Time stamp is", timeStamp));
running(); // => false
start();
running(); // => true

Definition

function createRAF(
callback: FrameRequestCallback,
): [running: Accessor<boolean>, start: VoidFunction, stop: VoidFunction];

Warning

To respect clients refresh rate, timeStamp should be used to calculate how much the animation should progress in a given frame, otherwise the animation will run faster on high refresh rate screens. As an example: A screen refreshing at 300fps will run the animations 5x faster than a screen with 60fps if you use other forms of time keeping that don't consider this. Please see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

targetFPS

A primitive for wrapping window.requestAnimationFrame callback function to limit the execution of the callback to specified number of FPS.

Loading playground…

Keep in mind that limiting FPS is achieved by not executing a callback if the frames are above defined limit. This can lead to not consistant frame duration.

The targetFPS primitive takes two arguments:

  • callback - The callback to run each allowed frame
  • fps - The target FPS limit
import createRAF, { targetFPS } from "@solid-primitives/raf";
const [running, start, stop] = createRAF(
targetFPS(timeStamp => console.log("Time stamp is", timeStamp), 60)
);
// the target fps value can be a reactive signal
const [fps, setFps] = createSignal(60);
createRAF(targetFPS((timestamp) => {...}, fps));

Definition

function targetFPS(
callback: FrameRequestCallback,
fps: MaybeAccessor<number>,
): FrameRequestCallback;

createMs

Loading playground…

Using createRAF and targetFPS to create a signal giving the passed milliseconds since it was called with a configurable frame rate, with some added methods for more control:

  • reset(): manually resetting the counter
  • running(): returns if the counter is currently setRunning
  • start(): restarts the counter if stopped
  • stop(): stops the counter if running

It takes the framerate as single argument, either as number or Accessor<number>. It also accepts the limit as an optional second argument, either as number or Accessor<number>; the counter is reset if the limit is passed.

import { createMs } from "@solid-primitives/raf";
const MovingRect() {
const ms = createMs(60);
return <rect x="0" y="0" width={1000 / 3000 * Math.min(ms(), 3000)} height="10" />;
}

Definition

function createMs(
fps: MaybeAccessor<number>,
limit?: MaybeAccessor<number>,
): Accessor<number> & {
reset: () => void;
running: () => boolean;
start: () => void;
stop: () => void;
};

createCallbacksSet

Loading playground…

Combines several callbacks into one. Use it with createRAF when several callbacks should run in the same requestAnimationFrame and share its timestamp. Callbacks live in a ReactiveSet, so you can add and remove them at any time.

// Type
function createCallbacksSet<T extends (...args: any) => void>(
...initialCallbacks: Array<T>
): [callback: (...args: Parameters<T>) => void, callbacksSet: ReactiveSet<T>];
// Example
import { createCallbacksSet, createRAF } from "@solid-primitives/raf";
const [callback, callbacks] = createCallbacksSet<FrameRequestCallback>();
const [running, start, stop] = createRAF(callback);
const move: FrameRequestCallback = ts => (el.style.transform = `translateX(${ts / 10}px)`);
callbacks.add(move);
start();
// later
callbacks.delete(move);

useGlobalRAF

Loading playground…

Returns a factory for callbacks that share one global requestAnimationFrame loop, so they all run in the same frame with the same timestamp. The loop is a single createRAF in a singleton root, combined with createCallbacksSet.

Each call returns [added, add, remove, running, start, stop]:

  • added, add, remove control whether this callback is in the shared loop. It is removed on cleanup.
  • running, start, stop control the shared loop itself, like createRAF.

Pass startWhenAdded (a boolean or accessor) to start the loop when the callback is added. The loop stops when the last callback is removed.

// Type
function useGlobalRAF(): (
callback: FrameRequestCallback,
startWhenAdded?: MaybeAccessor<boolean>,
) => [
added: Accessor<boolean>,
add: VoidFunction,
remove: VoidFunction,
running: Accessor<boolean>,
start: VoidFunction,
stop: VoidFunction,
];
// Example
import { flush } from "solid-js";
import { targetFPS, useGlobalRAF } from "@solid-primitives/raf";
const createGlobalRAFCallback = useGlobalRAF();
// Low frame rate, e.g. sampling a webcam
const [hasSampler, addSampler, removeSampler] = createGlobalRAFCallback(
targetFPS(() => {
/* ... */
}, 30),
true,
);
// High frame rate, e.g. drawing to a canvas
const [hasDraw, addDraw, removeDraw, running] = createGlobalRAFCallback(
targetFPS(() => {
/* ... */
}, 60),
true,
);
addSampler();
addDraw();
// `running()` updates once the write is flushed (tracked reads update automatically)
flush();
running(); // true, both run in the same frame

Warning Only use this when callbacks must run in the same frame, and keep each callback short: all of them share one frame's budget. To control several separate loops, use createCallbacksSet with your own createRAF instead.

createScheduledLoop

Loading playground…

Drives an external frameloop scheduler (for example motion's frame) with the same [running, start, stop] shape as createRAF. The loop is stopped on cleanup.

schedule receives the callback and must keep running it every frame until cancelled, returning an ID (not null or undefined) that cancel accepts. It is called once per start(), so a one-shot scheduler like requestAnimationFrame only runs the callback once; use createRAF for that. motion's frame.render returns the callback it scheduled, which is what cancelFrame takes:

// Type
function createScheduledLoop<
RequestID extends NonNullable<unknown>,
Callback extends (...args: Array<any>) => any,
>(
schedule: (callback: Callback) => RequestID,
cancel: (requestID: RequestID) => void,
): (callback: Callback) => [running: Accessor<boolean>, start: VoidFunction, stop: VoidFunction];
// Example
import { createScheduledLoop } from "@solid-primitives/raf";
import { cancelFrame, frame } from "motion";
const createMotionFrameRender = createScheduledLoop(
callback => frame.render(callback, true),
cancelFrame,
);
const [running, start, stop] = createMotionFrameRender(
data => (element.style.transform = "translateX(...)"),
);

Changelog

See CHANGELOG.md

Solid Primitives 2High-quality reactive primitives for building applications in Solid2
Community
githubdiscord