【问题标题】:RequestAnimationFrame speeds up/slows down periodicallyRequestAnimationFrame 定期加速/减速
【发布时间】:2020-01-01 01:02:48
【问题描述】:

据我了解,requestAnimationFrame 应该尽可能接近浏览器的帧速率,大约为 60fps。为了确保这确实发生,我一直在记录每个 requestAnimationFrame 调用的时间戳,如下所示:

function animate(now){
    console.log(now);
    window.requestAnimationFrame(animate);
}
window.requestAnimationFrame(animate);

Console.log 数据显示调用始终以大约 0.016674 毫秒的间隔发生,因此表明帧速率约为 60fps(准确地说是 59.9736116108912fps)。

Console.logs(示例数据):

Timestamp   FPS               (Current - previous) timestamp
------------------------------------------------------------
100.226     59.97361161       0.016674
116.9       59.97361161       0.016674
133.574     59.97361161       0.016674
   .             .               .
   .             .               .
150.248     59.97361161       0.016674
166.922     59.97361161       0.016674
183.596     59.97361161       0.016674
200.27      59.97361161       0.016674

到目前为止,requestAnimationFrame 调用以一致的时间间隔发生,并且没有任何调用滞后/执行太快。

但是,修改 animate() 函数的内容以执行相对更复杂的函数,会导致 requestAnimationFrame 调用不一致。

Console.logs(示例数据):

Timestamp       FPS               (Current - previous) timestamp
------------------------------------------------------------
7042.73         59.97361161       0.016674
7066.278        42.4664515        0.023548
7082.952        59.97361161       0.016674
7099.626        59.97361161       0.016674
   .                 .                .
   .                 .                .
17104.026       59.97361161       0.016674
17112.84        113.4558657       0.008814
17129.514       59.97361161       0.016674
17146.188       59.97361161       0.016674

从上面的数据样本中可以看出,时间戳差异和帧率不再稳定,有时发生得太早/太晚,导致帧率不一致。如果 requestAnimationFrame 一直被延迟调用,我会假设由于 JavaScript 的单线程特性,驻留在 animate() 函数中的复杂代码执行时间过长,从而导致 requestAnimationFrame 延迟调用。但是,由于 requestAnimationFrame 有时被调用得太早,因此情况似乎并非如此。

我的代码(骨架):

for (var counter = 0; counter < elements.length; counter ++) //10 elements
{
  //some other code

  animate(element);
}

function animate(element)
{
   // function invocation => performs some complex calculations and animates the element passed in as a parameter

   window.requestAnimationFrame(function() { animate(element) } );
}

从上面的代码 sn-p 中可以看出,requestAnimationFrame 在初始 for 循环中为每个元素调用多次。对于每个元素,RequestAnimationFrame 调用也打算无限进行。由于要执行的动画时间要求很高(动画时间在这种情况下非常重要,并且应该尽可能准确),因此 requestAnimationFrame 调用必须始终以一致的时间间隔发生。理想情况下,这些时间间隔应尽可能接近 0.016674 (≈ 60fps)。

关于要执行的动画的一些细节(在每个画布元素上):

我有一个非常具体的情况,我需要根据时间尽可能准确地绘制闪烁/闪烁动画,即画布颜色必须在指定的时间间隔内以一致的速率变化。因此,例如,画布颜色需要保持红色正好 0.025 秒,然后是另一个 0.025 秒,画布颜色设置为蓝色,然后是另一个 0.025 秒,画布为红色,依此类推......(动画应该对于每个元素,无限继续)。我目前的方法涉及跟踪动画循环本身内已过去的帧数(因此,每个 requestAnimationFrame 调用对应于单个帧)。

由于在 60Hz 显示器上无法实现 0.025 秒的精确帧长度,因此每个红/蓝画布周期应该是“近似值”。因此,考虑到 60Hz 显示器,创建一个完整的周期,画布最初是红色,然后是蓝色,总共需要 3 帧(1 秒/60 = 0.01666667 秒 * 3 帧 = 0.05 秒 =>一个完整的红色/蓝色循环所需的持续时间)。将 0.05 秒除以 2 将得到所需的帧长度(即 0.025 秒),但是由于这在 60Hz 监视器上无法实现,因此该周期近似为呈现 2 个红色画布帧,然后是单个蓝色帧(从而形成整个 3 帧周期)。不幸的是,即使考虑到显示器的刷新率,时间也会波动,从而导致不希望的不准确性。

最后的问题:

  1. 您能否澄清导致这种不一致的 requestAnimationFrame 行为的原因?

  2. 是否可以应用任何优化来确保 requestAnimationFrame 调用以一致的时间间隔执行?

  3. 如果我使用其他类型的功能(例如,Web Worker 与 setInterval() 函数结合使用),能否实现更好的计时精度?

【问题讨论】:

  • 您应该在setTimeout 中执行您的逻辑,并在requestAnimationFrame 中进行视觉更新
  • @GetOffMyLawn 但是 setTimeout 不是很不准确吗?从进行的测试以及一些在线资源来看,情况似乎如此。这同样适用于 setInterval。
  • 在回调完成执行后间隔开始 x 毫秒,因此当回调被再次调用时,复杂程度不同的任务可能会延迟或加速。
  • 您使用什么浏览器来生成这个113FPS 框架?不能复制这个。否则 1 如果有任何东西阻止浏览器,它将无法保持一致的帧速率。 2,取决于您在做什么/导致瓶颈的原因。一些操作可以卸载到 WebWorker,但不是全部。 3 取决于您到底要做什么。现在,您不应该假设任何帧速率,浏览器可能(并且实际上应该)使其适应显示器的帧速率。因此,两个用户使用两个不同的显示器(即使在同一浏览器+计算机上)可能会得到完全不同的结果。
  • 当你有这么短的帧率下降并且你不知道为什么时,GC总是一个很好的猜测。

标签: javascript html performance animation requestanimationframe


【解决方案1】:

requestAnimationFrame 将“尽力”以“一致”的帧速率运行。这并不能保证 60fps;它只是声明它将尽可能快地制作动画。

简而言之,该方法允许您在下一个可用的屏幕重绘上执行代码,从而避免与用户的浏览器同步以及硬件准备好对屏幕进行更改的猜测工作。

我们输入一个包含我们希望运行的代码的回调函数,requestAnimationFrame() 将在屏幕准备好接受下一个屏幕重绘时运行它

为了使您的动画保持不变,您必须根据实际增量计算回调中的数据,重新计算值,而不是假定 FPS 不变。

例如:

function moveit(timestamp, el, dist, duration){
    //if browser doesn't support requestAnimationFrame, generate our own timestamp using Date:
    var timestamp = timestamp || new Date().getTime()
    var runtime = timestamp - starttime
    var progress = runtime / duration
    progress = Math.min(progress, 1)
    el.style.left = (dist * progress).toFixed(2) + 'px'
    if (runtime < duration){ // if duration not met yet
        requestAnimationFrame(function(timestamp){ // call requestAnimationFrame again with parameters
            moveit(timestamp, el, dist, duration)
        })
    }
}

requestAnimationFrame(function(timestamp){
    starttime = timestamp || new Date().getTime() //if browser doesn't support requestAnimationFrame, generate our own timestamp using Date
    moveit(timestamp, adiv, 400, 2000) // 400px over 1 second
})

【讨论】:

  • 您好,谢谢您,这对于您在示例中提供的特定场景绝对有意义。但是,我有一个非常具体的情况,我需要根据时间尽可能准确地绘制闪烁动画,即画布颜色必须在指定的时间间隔内以一致的速率变化。因此,例如,画布颜色需要保持红色正好 0.025 秒,然后再保持 0.025 秒,将画布颜色设置为蓝色,然后再保持 0.025 秒,其中画布为红色,依此类推......(继续无限)。
  • 因此,对于这个特定场景,我目前不知道如何调整您的示例以满足这些要求。
  • @Questionnaire 可能有一种方法可以使画布的像素缓冲区在大约您想要的时间(+- 几微秒)成为您想要的颜色,但它无法被绘制正是按照这个速度。显示器的屏幕刷新率最终将与您的代码要求发生冲突,这是无法解决的。如果我们采用常见的 60Hz 速率,您的画布只能绘制 0.016 毫秒,或者介于 0.032 毫秒之间。
  • 正如@Kaiido 所说,您可以跳过帧、延迟帧渲染,但在 PERFECT 时间闪烁而不考虑 CPU 和刷新率量子上发生的情况,这是不可能的
  • 我同意这一点,刷新率肯定会干扰。但是,我打算在进行计算时考虑到这一点。很抱歉在我之前的评论/问题中没有提到这一点。以下评论中提供了更多详细信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多