多个requestAnimationFrame的性能表现优化

66

如果我正在做多个动画,性能方面是否可以添加多个requestAnimationFrame回调函数?例如:

function anim1() {
    // animate element 1
}

function anim2() {
    // animate element 2
}

function anim3() {
    // animate element 3
}

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

还是证明使用单个回调函数更糟糕吗:

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

我问这个问题是因为我不知道 requestAnimationFrame 在幕后发生了什么,当你多次调用它时,是否会排队回调函数?

我问这个问题是因为我不知道requestAnimationFrame在幕后发生了什么,当你多次调用它时,是否会排队回调函数?
4个回答

16

我认为这些答案都没有真正解释我所寻找的内容:“对requestAnimationFrame进行n次调用”是否会被防抖(即每帧出队列1次)或者所有调用都在下一帧中被调用。

当由requestAnimationFrame()排队的回调开始在单个帧中触发多个回调(mdn)

这表明后者,多个回调可能在同一帧中被调用。

通过以下测试,我证实了这一点。60赫兹的刷新率转换为17毫秒的周期。如果是前者,没有2个时间戳会相互间隔不到17毫秒,但事实并非如此。

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


9

你应该只使用一次requestAnimationFrame调用,因为调用requestAnimationFrame会形成堆栈。因此单个回调版本性能更佳。


25
你确定吗?根据http://www.w3.org/TR/animation-timing/#FrameRequestCallback,回调函数会被存储在一个列表中,并按顺序执行。为什么用一个包含3个动画的回调函数比用三个只包含1个动画的回调函数更有效率呢? - David Hellsing
2
@David,这是因为你少调用了两个函数。 - Erik Schierboom
6
@ErikSchierboom 是正确的,调用函数次数越少,栈中的开销就越小。然而,我可以看到对于不经常触发的短暂动画应用,将其放在主循环外可以简化编码。此外,在主循环的“热循环”内移除任何分支可能会减少JIT编译器因链接、取消链接、创建本机对象等而产生的抖动。 - mattdlockyer
8
实际性能比较:http://jsperf.com/single-raf-draw-calls-vs-multiple-raf-draw-calls 明显是3倍的性能损失,但我很想知道这实际上转化成了什么样的可感知的性能瓶颈。 - Lane
重要提示:为了更好地理解“幕后情况”,我发现多次调用同一函数可以使动画更快。 - Andrea Scarafoni

7

有人对此进行了基准测试,我们来谈谈...

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

我查看了性能比较(您也应该这么做)。如果您有不同意见,请随时提出。这些是在画布元素上的绘图原语。

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


我认为这段代码真正测试的是调用timestamp()函数7次与调用它2次之间的差异。看一下Chrome 46和47之间的差异。
- Chrome 46: 12k/秒(一次调用)与12k/秒(3次调用) - Chrome 47: 270k/秒(一次调用)与810k/秒(3次调用)
我认为这已经被优化得非常好了,没有什么区别。这只是在测量噪音。
我的结论是,这不需要为我的应用程序手动优化。
如果你担心性能问题,请看一下Chrome 59(1.8m ops/sec)和Chrome 71(506k ops/sec)之间的差异。

Chrome 71比Chrome 59更低效?! - oldboy
3
你错过了主要的点:这非常快,观察到的性能波动很大,因为你测量的是背景噪声而不是实际工作。 - spectras

-1

requestAnimationFrame绑定一个函数调用并返回frameID。请求多个帧与向事件添加多个事件侦听器不同-每个函数在另一个帧中调用。 因此,如果您连续(每个函数递归地重新调用自己)请求多个帧,则会失去所有更新都在一个帧内呈现的好处。因此,即使有高帧率动画也可能看起来不太平滑。

但是:您只能使用cancelAnimationFrame(frameID)取消所有方法,并且可能需要一些额外的代码来取消单个动画


6
这是不正确的,而且可能有些棘手,所以我理解您如何得出那个假设。在下一次重新绘制之前注册的任何请求都会被添加到帧中;每个帧中可以包含无限数量的请求。requestAnimationFrame返回的句柄是请求ID,对于该特定请求是唯一的,而不是与帧相关联的帧ID。要识别出给定请求排队的唯一帧,您需要使用传递给您已请求进行动画处理的函数的DOMHighResTimeStamp。您可以在MDN上阅读更多内容。 - Magnus Lind Oxlund

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接