【问题标题】:Web browser paint rate with multiple animations on the page网页上带有多个动画的 Web 浏览器绘制速率
【发布时间】:2026-01-22 13:15:02
【问题描述】:

我们可以使用requestAnimationFrame 在浏览器中绘制框架。但是,如果我们在同一个页面上有多个动画会发生什么?

例如,如果我们有 3 个自定义选取框和一些 2 个淡入淡出效果,因为每个效果都有自己的requestAnimationFrame,这是否意味着我们实际上在每个帧请求中绘制浏览器 5 次?

此外,如果我将文本滚动器 FPS 限制为 30FPS,但我的淡入淡出效果以 45FPS 运行,这并不意味着在 1 秒内我们总共运行 3*30 + 2*45 = 180帧画?

让 1 requestAnimationFrame 绘制我所有的动画会更好(考虑到我将页面上的所有动画都限制为相同的 FPS 速率)吗?结果,我最终只能每秒绘制 30-60 帧(取决于 FPS 限制)?

我只是在想办法尽可能减少 CPU 使用率。

【问题讨论】:

    标签: javascript performance animation requestanimationframe


    【解决方案1】:

    将多个效果合并到一个requestAnimationFrame 中相当容易

    您使用一组 javascript 对象来定义每个效果的时间:

    var timers=[];
    timers.push({delay:50,nextFireTime:0,doFunction:doEffect1,counter:0});
    timers.push({delay:500,nextFireTime:0,doFunction:doEffect2,counter:0});
    timers.push({delay:5000,nextFireTime:0,doFunction:doEffect3,counter:0});
    

    您可以使用一个 requestAnimationFrame 循环,该循环遍历循环并根据 nextFireTime 触发每个效果

    function timerLoop(currentTime){
        // request another loop
        requestAnimationFrame(timerLoop);   
        // iterate through each timer
        for(var i=0;i<timers.length;i++){
            // if this timer has reached its 
            //     next scheduled trigger time...
            if(currentTime>timers[i].nextFireTime){
                var t=timers[i];
                // ...then do this effect
                t.doFunction(t,i);
                // and reset the timer to fire again in the future
                t.nextFireTime=currentTime+t.delay;
            }
        }
    }
    

    下面是示例代码和演示:

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    
    
    var timers=[];
    timers.push({delay:50,nextFireTime:0,doFunction:doTimers,counter:0});
    timers.push({delay:500,nextFireTime:0,doFunction:doTimers,counter:0});
    timers.push({delay:5000,nextFireTime:0,doFunction:doTimers,counter:0});
    //
    requestAnimationFrame(timerLoop);
    //
    function timerLoop(currentTime){
      // request another loop
      requestAnimationFrame(timerLoop);   
      // iterate through each timer
      for(var i=0;i<timers.length;i++){
        // if this timer has reached its 
        //     next scheduled trigger time...
        if(currentTime>timers[i].nextFireTime){
          var t=timers[i];
          // ...then do this effect
          t.doFunction(t,i);
          // and reset the timer to fire again in the future
          t.nextFireTime=currentTime+t.delay;
        }
      }
    }
    //
    function doTimers(t,i){ 
      // this demo just calls this one effect function
      // but you would call separate effect functions 
      // for your marquis & fades.
      ctx.clearRect(0,100+i*20-20,cw,20);
      ctx.fillText('Timer#'+i+' with '+t.delay+'ms delay has fired '+(++t.counter)+' times.',20,100+20*i);    
    }
    body{ background-color: ivory; padding:10px; }
    #canvas{border:1px solid red;}
    &lt;canvas id="canvas" width=300 height=300&gt;&lt;/canvas&gt;

    【讨论】:

    • 非常感谢!非常感激。我读过一些关于requestAnimationFrame 的文章,似乎requestAnimationFrame 会一次收集一幅画的所有动画。你认为我误解了我读到的内容吗?在您的timeLoop 函数中,您有一个参数currentTime,但您在调用requestAnimationFrame 时从未将它作为参数传递,那么currentTime 变量如何被初始化和更新?最后一个问题,我应该使用canvas 代替divs 来制作我的动画吗?它对 CPU 更友好吗?
    【解决方案2】:

    requestAnimationFrame指的是浏览器内部渲染整个网页的一帧的机制。当您说requestAnimationFrame 时,您并不是在告诉它为您创建某些东西,而是要求它告诉您何时渲染下一帧,以便您可以将您的动作与它挂钩。

    因此,您可以根据需要多次使用requestAnimationFrame,并且浏览器将通过在框架(您请求的)可用时运行它们来自动同步您的操作。

    当然,性能方面最好避免每帧多次调用函数(因为它们需要在每一帧内一个接一个地堆栈和执行),但从同步的角度来看,它完全无关紧要。此外,您不可能将任何帧或颜料相乘。

    【讨论】: