Skip to content

Conversation

@royeden
Copy link

@royeden royeden commented Dec 3, 2025

  • Added useFrameloop util to use unified request animation frame calls.
  • Added createScheduledFrameloop to handle request animation frame from external sources.
  • Fixed createRAF cleanup for id 0 by using null instead.

Addresses this proposal #822.

- Added `useFrameloop` util to use unified request animation frame
calls.
- Added `createScheduledFrameloop` to handle request animation frame
from external sources.
- Fixed `createRAF` cleanup for id `0` by using `null` instead.
@changeset-bot
Copy link

changeset-bot bot commented Dec 3, 2025

⚠️ No Changeset found

Latest commit: d005f2e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

}

/**
* Returns an advanced primitive factory function (that has an API similar to `createRAF`) to handle multiple animation frame callbacks in a single batched `requestAnimationFrame`, avoiding the overhead of scheduling multiple animation frames outside of a batch and making them all sync on the same delta.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explanation is way too complex. "A version of createRAF that batches multiple frames within the same render cycle instead of skipping them ."

*
* The idea behind this is for more complex use cases, where you need scheduling and want to avoid potential issues arising from running more than one `requestAnimationFrame`.
*
* @see https://github.com/solidjs-community/solid-primitives/tree/main/packages/raf#createScheduledFrameloop
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You seem to have forgotten to update the README.md

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry for that!

automatic?: MaybeAccessor<boolean>,
) => [
queued: Accessor<boolean>,
queue: VoidFunction,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I generally dislike this addition, but why not extract the queue logic into a separate primitive that can also be used with the normal createRAF?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could I get some guidance on how that would be structured? Do you propose adding the queue logic as a separate create helper?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like

function queueCallbacks(
  ...initialCallbacks: (() => void)[]
): [callback: () => void, { add: (cb) => void, del: (cb) => void }] {
  ...
}

@royeden
Copy link
Author

royeden commented Dec 21, 2025

Sorry for the delay on this, the end of the year tends to be really busy. I was starting to re-visit the approach itself and maybe landed into a nicer primitive set, want to hear your opinions on this @atk (I can leave it as an update on this PR, but I don't think this is the right approach for this):

createFrameScheduler

A function with this signature that allows scheduling with external frameloop utils like motion's frame:

type CreateScheduledFrame<Callback extends (...args: any) => any> = 
  (callback: Callback) => [running: Accessor<boolean>, start: VoidFunction, stop: VoidFunction];

createFrameScheduler<
  // NonNullable is used here so we know nothing is scheduled when the value is `null`
  RequestID extends NonNullable<unknown>,
  Callback extends (...args: any) => any
>(
  schedule: (callback: Callback) => RequestID,
  cancel: (requestID: RequestID) => void,
): CreateScheduledFrame<Callback>

createFrameloopScheduler

A function with this signature that allows scheduling with functions that need to run in a loop for the callback to re-run:

type CreateScheduledLoop<Callback extends (...args: any) => any> = 
  (callback: Callback) => [running: Accessor<boolean>, start: VoidFunction, stop: VoidFunction];

createFrameScheduler<
  // NonNullable is used here so we know nothing is scheduled when the value is `null`
  RequestID extends NonNullable<unknown>,
  Callback extends (...args: any) => any
>(
  schedule: (callback: Callback) => RequestID,
  cancel: (requestID: RequestID) => void,
): CreateScheduledLoop<Callback>

Why?

These primitives would allow us to compose any handlers with the running, start, stop logic, so you could basically do create your own utils (not 100% sure if these should exist under the raf package anymore either).

They both work with the same implementation that is used in createRAF.

Examples:

createRAF (requestAnimationFrame):

export const createRAF = createFrameloopScheduler(
  window.requestAnimationFrame,
  window.cancelAnimationFrame
);

createRIC (requestIdleCallback):

export const createRIC = createFrameloopScheduler(
  window.requestIdleCallback,
  window.cancelIdleCallback
);

export const createRIC100ms = createFrameloopScheduler(
  (callback: IdleRequestCallback) => window.requestIdleCallback(callback, { timeout: 100 }),
  window.cancelIdleCallback
);

motion's frame utils:

import { cancelFrame, frame } from 'motion';
export const createMotionRead = createScheduledFrame(
  callback => frame.read(callback, true),
  cancelFrame
);

export const createMotionUpdate = createScheduledFrame(
  callback => frame.update(callback, true),
  cancelFrame
);

export const createMotionRender = createScheduledFrame(
  callback => frame.render(callback, true),
  cancelFrame
);

Copy link
Member

@atk atk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see my last points addressed, also, the tests seem to fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add your primitives to primitive.list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants