Multiple requestAnimationFrame performance
Asked Answered
P

4

67

If I’m doing multiple animations, is it OK performance-wise to add multiple requestAnimationFrame callbacks? F.ex:

function anim1() {
    // animate element 1
}

function anim2() {
    // animate element 2
}

function anim3() {
    // animate element 3
}

requestAnimationFrame(anim1);
requestAnimationFrame(anim2);
requestAnimationFrame(anim3);

Or is it proven worse than using a single callback:

(function anim() {
    requestAnimationFrame(anim);
    anim1();
    anim2();
    anim3();
}());

I’m asking because I don’t really know what is going on behind the scenes, is requestAnimationFrame queuing callbacks when you call it multiple times?

Pipage answered 14/6, 2013 at 8:0 Comment(0)
F
17

I don't think any of these answers really explained what I was looking for: "do n calls to requestAnimationFrame" get debounced (i.e. dequeued 1 per frame) or all get invoked in the next frame.

When callbacks queued by requestAnimationFrame() begin to fire multiple callbacks in a single frame (mdn)

This suggests the latter, multiple callbacks can be invoked in the same frame.

I confirmed with the following test. A 60 hz refresh rate translates to a 17ms period. If it were the former, no 2 timestamps would be within 17ms of each other, but that was not the case.

let sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

let update = async timestamp => {
  console.log('update called', timestamp)
  await sleep(10);
  requestAnimationFrame(update);
}

requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
requestAnimationFrame(update);
Faxen answered 22/4, 2020 at 14:22 Comment(0)
N
9

You should be using only one requestAnimationFrame call as calls to requestAnimationFrame do stack. The single callback version is thus more performant.

Nay answered 14/6, 2013 at 8:10 Comment(5)
Are you sure about this? According to w3.org/TR/animation-timing/#FrameRequestCallback, the callbacks are stored in a list and executed in order. Why would a single callback with 3 animations be more efficient than 3 callbacks with 1 animation?Pipage
@David For the simple fact that you make 2 function calls less.Nay
@ErikSchierboom is right, less function calls will equal less overhead in the stack. However, I can see the application especially for shortlived animations that fire infrequently to live outside the main loop for coding simplicity. Additionally, removing any branching from within a "hot loop" in your main loop may reduce any JIT compiler thrashing due to linking, unlinking, creating native objects etc...Everett
actual performance comparison: jsperf.com/single-raf-draw-calls-vs-multiple-raf-draw-calls it's clearly a 3x performance hit, but I'd be curious to know what kind of perceptible performance bottleneck that actually translates toGreasewood
as important note, to better understand the 'behind the scene', i discovered that the multiple invocation with the same function, make the animation faster.Clew
S
8

Someone benchmarked this. Let's talk...

https://jsperf.com/single-raf-draw-calls-vs-multiple-raf-draw-calls

I looked at the performance comparison (you should too). You're welcome to disagree. These are drawing primitives on a canvas element.

        function timeStamp() {
          return window.performance && window.performance.now ? window.performance.now() : new Date().getTime();
        }

        function frame() {
            drawCircle();
            drawLines();
            drawRect();
        }

        function render() {
            if (timeStamp() >= (time || timeStamp())) {
                time = timeStamp() + delayDraw;
                frame();
            } 
            requestAnimationFrame(render);
        }

        function render1() {
            if (timeStamp() >= (time || timeStamp())) {
                time = timeStamp() + delayDraw;
                drawCircle();
            } 
            requestAnimationFrame(render1);
        }

        function render2() {
            if (timeStamp() >= (time || timeStamp())) {
                time = timeStamp() + delayDraw;
                drawRect();
            } 
            requestAnimationFrame(render2);
        }

        function render3() {
            if (timeStamp() >= (time || timeStamp())) {
                time = timeStamp() + delayDraw;
                drawLines();
            } 
            requestAnimationFrame(render3);
        }

I think this code is really benchmarking 7 calls to timestamp() vs 2 calls to timestamp(). Look at the difference between Chrome 46 and 47.

  • Chrome 46: 12k/sec (one call) vs 12k/sec (3 calls)
  • Chrome 47: 270k/sec (one call) vs 810k/sec (3 calls)

I think this is so well optimized that it doesn't make a difference. This is just measuring noise at this point.

My takeaway is this doesn't need to be hand-optimized for my application.

If you're worried about performance look at the difference between Chrome 59 (1.8m ops/sec) vs Chrome 71 (506k ops/sec).

Subaqueous answered 24/3, 2019 at 17:3 Comment(2)
chrome 71 is more inefficient than chrome 59?!Indelicacy
@oldboy> you missed the main point: this is so fast that observed performance fluctuates greatly. Because you're measuring background noise rather than actual work.Poston
C
-1

The requestAnimationFrame binds a function call and returns the frameID. Requesting multiple frames is NOT the same like adding multiple event listeners to an event- each of your functions is called in another frame. So if you continuously (each function recalls itself recursively) request several frames you're loosing the benefit that all updates are rendered within one frame. So even if there is a high framerate animations may not look that smooth.

But: you can only use cancelAnimationFrame(frameID) for all methods and may need some extra code to cancel single animations

Cruise answered 25/3, 2018 at 22:18 Comment(1)
This is not correct, and it can be a little tricky to keep up with, so I understand how you reached that assumption. Any requests registered before the execution of the next repaint gets added to the frame; there's no limit on how many requests can be included in each frame. The handle returned by requestAnimationFrame is a request ID, unique to that particular request, not a frame ID unique to the frame. To identify the unique frame a given request is queued for, you use the DOMHighResTimeStamp passed to the function you've requested be animated. You can read more on MDN.Naresh

© 2022 - 2024 — McMap. All rights reserved.