For more tips like this, sign up to the weekly newsletter!

How to get consistent timing with requestAnimationFrame

requestAnimationFrame's callback gets a parameter with the current timestamp. It is a high-resolution number, similar to the result of performance.now().

So much so, it seems to be a good practice to store the result of performance.now() before calling requestAnimationFrame() and compare the two timestamps to see how much time has passed.

But this yields unexpected results:

const start = performance.now();
  
requestAnimationFrame((timestamp) => {
  console.log(timestamp - start);
  // possibly negative
})

A negative number means the timestamp is for an earlier time than start.

How can this be?

As it turns out, the timestamp is the time the first requestAnimationFrame is fired for the current frame, which might happen before the performance.now() call, hence the negative number.

This can cause problems, depending on the application logic.

How can you get more consistent timing?

Use performance.now() only

performance.now() in itself is a good indicator of the current time. Using that alone provides a clean and consistent way to track the time passed:

const start = performance.now();

requestAnimationFrame(() => {
  console.log(performance.now() - start);
  // Always positive
})
Use timestamp only

The other solution is to ditch performance.now() altogether and rely on the timestamps:

requestAnimationFrame((start) => {
  requestAnimationFrame((timestamp) => {
    console.log(timestamp - start);
    // Always positive
  })
})

This approach requires some custom logic to handle the first callback firing but otherwise is a viable solution.

Try it
References
Learn more: