A tiny frame scheduler for performantly batching reads, updates and renders.

Segregating actions that read and write to the DOM will avoid layout thrashing.

It’s also a way of ensuring order of execution across a frame. For instance, Popmotion batches updates on the update step, and Stylefire batches renders on the render step, allowing independent animation of transform properties.


npm install framesync


Framesync splits a frame into discrete read, update, render and postRender steps.

Scheduling functions

Functions can be scheduled to different parts of the render loop with sync.

import sync from 'framesync';

It provides four functions, one for scheduling a function to run on each part of the frame:

sync.update(() => {});

Frame data

Each function is provided data about the current frame:

sync.update({ delta, timestamp }) => {});
  • delta: Time since last frame (in milliseconds)
  • timestamp: Timestamp of the current frame.

This object is recycled across frames, so values should be destructured if intended to be used asynchronously.

Keep alive

We can run a function as an ongoing process by passing true as the second parameter:

let count = 0;

sync.update(() => count++, true);

This will keep the process running until it’s actively cancelled.

Run immediately

The third parameter, immediate, can be used to sync a function on the current frame step.

By default, Framesync will schedule functions to run the next time that frame step is fired:

sync.update(({ timestamp }) => {
  // The following function will run on the subsequent frame:
  sync.update((frame) => frame.timestamp !== timestamp);

By setting immediate to true, we can add this at the end of the current step:

sync.update(({ timestamp }) => {
  // The following function will run on the **current** frame:
    (frame) => frame.timestamp === timestamp,


Synced processes can be cancelled with the cancelSync function:

import sync, { cancelSync } from 'framesync';

let count = 0;

const process = sync.render(() => {
  if (count >= 10) cancelSync.render(process);
}, true);