【问题标题】:requestAnimationFrame [now] vs performance.now() time discrepancyrequestAnimationFrame [now] 与 performance.now() 时间差异
【发布时间】:2016-07-13 19:34:26
【问题描述】:

假设:rAF now 时间是在回调集合全部被触发时计算的。因此,在调用该帧的第一个回调之前发生的任何阻塞都不会影响 rAF now 并且它是准确的 - 至少对于第一个回调而言。

在触发 rAF 集之前进行的任何 performance.now() 测量都应该早于 rAF now。

测试:记录before(任何事情发生之前的基准时间)。设置下一个 rAF。比较 rAF now 和实际的 performance.now() 和 before,看看它们有多么不同。

预期结果:

var before = performance.now(), frames = ["with blocking", "with no blocking"], calls = 0;
requestAnimationFrame(function frame(rAFnow) {
  var actual = performance.now();
  console.log("frame " + (calls + 1) + " " + frames[calls] + ":");
  console.log("before frame -> rAF now: " + (rAFnow - before));
  console.log("before frame -> rAF actual: " + (actual - before));

  if (++calls < frames.length) { before = actual; requestAnimationFrame(frame); }
});

// blocking
for (var i = 0, l = 0; i < 10000000; i++) {
    l += i;
}

观察:当帧开始前出现阻塞时,rAF now 时间有时不正确,即使对于第一帧也是如此。有时第一帧的now实际上比记录的before时间早。

无论是否在帧之前发生阻塞,帧内时间rAFnow 经常会早于帧前时间before--即使我设置了 rAF after 我进行了第一次测量。这也可以在没有任何阻塞的情况下发生,尽管这种情况很少见。

(我大部分时间在第一个阻塞帧上出现计时错误。在其他帧上出现问题的情况很少见,但如果您尝试运行几次,偶尔会发生。)

通过更广泛的测试,我发现回调之前阻塞的糟糕时间:100 帧中的 1%,无阻塞:约 400 帧中的 0.21645021645021645%,这似乎是由打开窗口或其他一些潜在的 CPU 密集型操作引起的由用户。

所以这是相当罕见的,但问题是这根本不应该发生。如果你想用它们做有用的事情,模拟时间、动画等,那么你需要这些时间才有意义。

我已经考虑了人们所说的话,但也许我仍然不明白事情是如何运作的。如果这一切都符合规范,我会喜欢一些伪代码来巩固我的想法。

更重要的是,如果有人对我如何解决这些问题有任何建议,那就太好了。我唯一能想到的就是每帧进行我自己的performance.now() 测量并使用它——但这似乎有点浪费,让它每帧有效地运行两次,在任何触发事件之上等等。

【问题讨论】:

  • 我在你的 sn-p 中添加了一个停止状态。随意回滚,但这样代码实际上会在某个时候完成:)。
  • 不,这很酷。谢谢,@MikeMcCaughan。

标签: javascript timing requestanimationframe


【解决方案1】:

传入requestAnimationFrame()回调的时间戳是动画帧开始的时间。在同一帧期间调用的多个回调都接收相同的时间戳。因此,如果performance.now() 返回一个时间before 参数值会很奇怪,但如果它是after 那并不奇怪。

Here's the relevant specification:

当用户代理要为带有时间戳现在的文档文档运行动画帧回调时,它必须运行以下步骤:

  1. 如果文档对象的 hidden 属性返回的值为 true,则中止这些步骤。 [页面可见性]

  2. 让回调是文档的动画帧回调列表中的条目列表,按照它们添加到列表中的顺序。

  3. 将文档的动画帧回调列表设置为空列表。

  4. 对于回调中的每个条目,按顺序:调用 Web IDL 回调函数,传递 now 作为唯一参数,如果抛出异常,则报告异常。

所以您已经为下一个动画帧注册了一个回调(假设只有一个)。嘀嘀嘀嘀,BOOM,该动画帧发生的时间:

  1. JavaScript 运行时会记录现在的时间和标签。
  2. 运行时会生成已注册动画帧回调列表的临时副本,并清除实际列表(这样如果需要很长时间以致下一个动画帧出现,它们就不会被意外调用)。
  3. 列表中只有一件事:您的回调。系统以 now 作为参数调用它。
  4. 您的回调开始运行。也许它以前从未运行过,所以 JavaScript 优化器可能需要做一些工作。或者,操作系统可能会将线程切换到其他系统进程,例如启动磁盘缓冲区刷新或处理某些网络流量,或其他许多事情。
  5. 哦,对了,您的回电。浏览器再次获得 CPU 并且您的回调代码开始运行。
  6. 您的代码调用 performance.now() 并将其与作为参数传入的 now 值进行比较。

因为在第 1 步和第 6 步之间可能会经过短暂但不可忽略的时间,performance.now() 的返回值可能表明已经过去了几微秒,甚至超过几微秒。 这是完全正常的行为。

【讨论】:

  • 您是说 any 回调,即使是非 rAF 回调也将具有相同的 performance.now() 值?情况似乎并非如此。如果您的意思是多个 rAF 回调,我只使用一个 rAF。
  • 我进行了编辑以更好地解释正在发生的事情。 (我不确定你是否会收到通知?)
  • @Whothehellisthat 不不不。当事件发生在触发动画帧回调处理的 JavaScript 运行时内部时,时间戳会在该过程开始时一次记录下来。 requestAnimationFrame() 注册的每个回调都传递完全相同的时间值。这实际上与对performance.now() 的后续调用无关,只是它们显然都将在收集初始时间之后发生。
  • 根据系统上发生的情况(其他进程、操作系统工作等),在动画帧内部收集帧开始时间戳和回调中的代码运行。特别是,系统不会将调用performance.now() 的结果传递给每个回调。因此,即使只有一个注册的回调,它的参数比performance.now() 返回的数字小也就不足为奇了。
  • @Whothehellisthat 答案扩展了一点。
【解决方案2】:

我在 chrome 上遇到了同样的问题,其中对 performance.now () 的调用将返回一个高于 now 值传递给 window.requestAnimationFrame () 进行的后续回调的值

我的解决方法是使用在第一个window.requestAnimationFrame () 中传递给回调的now 设置before,而不是performance.now ()。似乎只使用两个函数之一来测量时间可以保证进度值。

我希望这可以帮助其他遭受此错误的人。

【讨论】:

    猜你喜欢
    • 2015-11-27
    • 2020-05-06
    • 2015-08-28
    • 2012-10-27
    • 2015-12-09
    • 1970-01-01
    • 1970-01-01
    • 2018-02-05
    • 2011-01-05
    相关资源
    最近更新 更多