requestAnimationFrame的时间戳不可靠

时间:2020-10-02 19:41:24

标签: javascript requestanimationframe

我认为requestAnimationFrame传递的时间戳参数计算错误(在Chrome和Firefox中进行了测试)。

在下面的代码段中,我有一个循环大约需要300ms(您可能需要调整循环迭代的次数)。 计算得出的delta应该始终大于循环的打印“持续时间”。 奇怪的是,有时它变慢了,有时却没有。为什么?

let timeElapsed = 0;
let animationID;


const loop = timestamp => {
  const delta = timestamp - timeElapsed;
  timeElapsed = timestamp;

  console.log('delta', delta);
  
  // some heavy load for the frame
  const start = performance.now();
  let sum = 0;
  for (let i = 0; i < 10000000; i++) {
    sum += i ** i;
  }
  console.warn('duration', performance.now() - start);

  animationID = requestAnimationFrame(loop)
}

animationID = requestAnimationFrame(loop);

setTimeout(() => {
    cancelAnimationFrame(animationID);
}, 2000);

jsFiddle:https://jsfiddle.net/Kritten/ohd1ysmg/53/

请不要让摘要在两秒钟后停止。

1 个答案:

答案 0 :(得分:2)

至少在Blink和Gecko中,传递给rAF回调的时间戳是最后一个VSync脉冲之一。

在代码段中,CPU和事件循环被锁定了约300ms,但是监视器仍以相同的速率并行发出其VSync脉冲。

浏览器完成300ms计算后,必须安排新的动画帧。
在下一个事件循环迭代中,它将检查监视器是否发送了新的VSync脉冲,并且自发送以来(在60Hz上大约进行了18次),它将几乎立即执行新的rAF回调。

传递给rAF回调的时间戳确实可能是您上一次回调结束之前的时间之一,因为事件循环在上一个VSync脉冲之后释放了。

强制执行此操作的一种方法是使计算的持续时间仅比帧的持续时间长一点,例如,在60Hz监视器上,VSync脉冲将每16.67ms发生一次,因此,如果将事件循环锁定16.7ms,我们可以一定要确保时间戳增量比实际计算时间短:

let stopped = false;
let perf_elapsed = performance.now();
let timestamp_elapsed = 0;
let computation_time = 0;
let raf_id;

const loop = timestamp => {

  const perf_now = performance.now();

  const timestamp_delta = +(timestamp - timestamp_elapsed).toFixed(2);
  timestamp_elapsed = timestamp;

  const perf_delta = +(perf_now - perf_elapsed).toFixed(2);
  perf_elapsed = perf_now;

  const ERROR = timestamp_delta < computation_time;
  if (computation_time) {
    console.log({
      computation_time,
      timestamp_delta,
      perf_delta,
      ERROR
    });
  }

  // some heavy load for the frame
  const computation_start = performance.now();
  const frame_duration = 1000 / frequency.value;
  const computation_duration = (Math.ceil(frame_duration * 10) + 1) / 10; // add 0.1 ms 
  while (performance.now() - computation_start < computation_duration) {}
  computation_time = performance.now() - computation_start;

  
  raf_id = requestAnimationFrame(loop)
  
}

frequency.oninput = evt => {
  cancelAnimationFrame( raf_id );
  console.clear();
  raf_id = requestAnimationFrame(loop);
  setTimeout(() => {
    cancelAnimationFrame( raf_id );
  }, 2000);
};
frequency.oninput();
In case your monitor has a different frame-rate than th common 60Hz, you can insert it here:

<input type="number" id="frequency" value="60" steps="0.1">

所以我想在此时间戳和performance.now()之间使用什么是您的调用,时间戳告诉您帧开始的时间,performance.now()告诉您代码何时执行,如果需要,您可以使用两者。即使没有这么大的计算跨框架,您也可以很好地安排其他任务,而这要花几毫秒才能完成,甚至需要执行大量的CSS合成,您也无法知道。 / p>