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?


Solution 1:

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);

Solution 2:

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

Solution 3:

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).