Iamisti
Iamisti

Reputation: 1710

Requestanimationframe implementation: recursive / recursive fps limited / one-off

I've studied requestanimationframe documentation and looked for many posts about the usage of it, but i still haven't get a clear answer on one of my thought.

I understand that requestanimationframe is scheduling a task to be executed right at the beginning of the next frame, so the code that does dom manipulations will have a better chance to be finished and painted before the pain cyle. (unless setInterval or setTimeout which usually executes a lot later, causing the well known 'running out of time before the frame gets painted' => dropping frames).

1. The recursive way

The simplest example to use requestanimation frame is the following:

function animate() {
  requestAnimationFrame(animate);

  // drawing code comes here
}
requestAnimationFrame(animate);

This will give you a smooth animation if you have something that needs to be updated frequently, and also giving you the benefit of not dropping any frames during your animations. This will usually gives you 60fps animations, but if your browser and screen supports 144hz/fps, then you can easily end up having 144fps animations (6.95 ms cycle).

2. Fps limited animations

Other examples also introduce ways to limit the fps to a certain number. The following code snippnet shows how to limit your fps to 30 fps:

const fpsInterval = 1000 / 30;
let previousTime = 0;
function animate(time) {
  requestAnimationFrame(animate);

  const deltaTime = time - previousTime;

  if (deltaTime > fpsInterval) {
    // Get ready for next frame by setting then=now, but also adjust for your
    // specified fpsInterval not being a multiple of RAF's interval (16.7ms)
    previousTime = time - (deltaTime % fpsInterval);
  }

  // drawing code comes here
}
requestAnimationFrame(animate);

3. One-off animations

I've been wondering a third case, when you just want your animation to be scheduled precisely, even if you have 1 or just a few amount of updates in each second. A best example is when you have a websocket connection and each update will introduce a dom manipulation, but the update rate is far too low to do it in a recursive way.

// setting up websocket connection

ws.onmessage = (data) => {
  // changing application state
  myApplicationState = JSON.parse(data);

  requestAnimationFrame(animate);
}

function animate () {
    // drawing code comes here
}

Now here is my question for you all:

Does this make sense to call requestanimationframe right from the callback of a websocket onmessage function, or should i be using the recursive way?

So far I haven't tested it (in progress), but i have a feeling it does still going to give you the benefit of well-timed animations that can be executed without dropping a frame.

My real-life example is similar, i only have 5 messages in a second and i'd like to call requestanimationframe ONLY 5 times in a second.

My thought of doing this vs the recursive way:

My initial measures were the following. I've spin up chrome profiling and run it for 10 seconds and measured the script execution times (we're not measuring render or paint since they are basically identical):

Script execution times:

While recursive requestanimationframe solution is giving you a super smooth and good user experience, it's also very costy for your CPU and execution times. If you have multiple components doing animations with recursive requestanimationframe, you're going to hit a CPU bottleneck pretty soon.

Oddly this last case causing some fps drops, which I do not understand. My understanding is that you can call requestanimationframe whenever you want and it'll only execute the begginning of the next frame. But it seems there is something i dont know about.

Here is a picture of what is happening. I still don't understand it. requestanimationframe function was called before the end of the frame, but somehow because it was part of a bigger function call, it's marked as 'dropped' in chrome. Wonder if that's just a bug in the chrome profiling or was it for real dropped.

enter image description here

I wonder what you guys thinking about this topic. I'll update this post with some chrome performance metrics soon.

Upvotes: 2

Views: 431

Answers (1)

Kaiido
Kaiido

Reputation: 137133

There seems to some misconception about requestAnimationFrame (rAF) magically preventing dropped frames by ensuring that whatever is executed in it will somehow run fast enough. It doesn't.

requestAnimationFrame is just a timer for "right before the next paint"*.

Its main goal is to limit the number of callbacks to just what is needed, avoiding to waste drawing operations which won't even be rendered on screen.
It does actually allow to drop frames smartly, so if one execution took the time of 3 frames to render, it won't stupidly try to execute the 3 missing frames as soon as possible, instead it will nicely discard them and get your script ready to get back from this hiccup.

So using it for updating something that doesn't match the screen refresh rate is not very useful.
One should remember that calling requestAnimationFrame is not free, this will mark the document as animated and force the event-loop to enter the update-the-rendering steps, which in itself has some performance costs. So if what you are doing in there is not going to update the rendering of the page, it can actually be detrimental to wrap your callback in a rAF callback.
Still, there are cases where it could make sense, for instance in complex pages it may be good to have a method that batches all the changes to the DOM in a rAF callback, and have all the scripts that need to access the CSSOM boxes before these changes take effect, thus avoiding useless and costly reflows.
An other case is to avoid executing scripts when the page is in the background: rAF is heavily throttled when in background and if you have some script running that doesn't need to run when the page is hidden (e.g a clock or similar), it may make sense to wrap a timer in a rAF callback to take advantage of this heavy throttling.


*Actually both Chrome and Firefox have broken this conception. In these browsers if you call requestAnimationFrame(cb) from a non-animated document (a document where no animation frame callback is scheduled, and no mouse-event occurred in the last frame), they will force the update the rendering steps to fire immediately, making this frame not synced with the monitor (and, in Chrome, sometimes not even rendered on the screen at all).

Upvotes: 2

Related Questions