requestAnimationFrame的时间戳不可靠 [英] timestamp of requestAnimationFrame is not reliable
问题描述
我认为 requestAnimationFrame
传递的时间戳参数计算错误(在Chrome和Firefox中进行了测试).
在下面的代码段中,我有一个循环,大约需要一个循环.300ms(您可能需要调整循环迭代的次数).计算出的 delta
应该始终大于循环的打印持续时间".奇怪的是,有时它变慢了,有时却没有.为什么?
let timeElapsed = 0;让animationID;const loop =时间戳=>{const delta = timestamp-timeElapsed;timeElapsed =时间戳;console.log('delta',delta);//框架有些沉重const start = performance.now();令总和= 0;for(令i = 0; i< 10000000; i ++){和+ = i ** i;}console.warn('duration',performance.now()-开始);animationID = requestAnimationFrame(循环)}animationID = requestAnimationFrame(loop);setTimeout(()=> {cancelAnimationFrame(animationID);},2000年);
jsFiddle: https://jsfiddle.net/Kritten/ohd1ysmg/53/
请不要使该代码段在两秒钟后停止.
至少在Blink和Gecko中,传递给rAF回调的时间戳是最后一个
let stop = false;让perf_elapsed = performance.now();让timestamp_elapsed = 0;令Calculation_time = 0;让raf_id;const loop =时间戳=>{const perf_now = performance.now();const timestamp_delta = +(timestamp-timestamp_elapsed).toFixed(2);timestamp_elapsed =时间戳;const perf_delta = +(perf_now-perf_elapsed).toFixed(2);perf_elapsed = perf_now;const错误= timestamp_delta<计算时间;如果(computation_time){console.log({计算时间,timestamp_delta,perf_delta,错误});}//框架有些沉重const Calculation_start = performance.now();const frame_duration = 1000/frequency.value;const Calculation_duration =(Math.ceil(frame_duration * 10)+ 1)/10;//增加0.1毫秒while(performance.now()-Calculation_start< Calculation_duration){}Calculation_time = performance.now()-Calculation_start;raf_id = requestAnimationFrame(loop)}频率.oninput= evt =>{cancelAnimationFrame(raf_id);console.clear();raf_id = requestAnimationFrame(loop);setTimeout(()=> {cancelAnimationFrame(raf_id);},2000年);};frequency.oninput();
如果您的显示器的帧频与普通60Hz不同,则可以在此处插入:<输入type ="number" id =频率" value ="60"步骤="0.1">
所以我猜想在此时间戳和 performance.now()
之间使用的是您的调用,时间戳告诉您帧何时开始, performance.now()
会告诉您代码何时执行,如果需要,您可以同时使用两者.即使没有这么大的计算跨帧,您也可以很好地安排其他任务在您之前完成,而这需要花费几毫秒才能完成,甚至需要执行较大的CSS合成,而且您没有真正的方法知道./p>
I think the timestamp argument passed by requestAnimationFrame
is computed wrongly (tested in Chrome and Firefox).
In the snippet below, I have a loop which takes approx. 300ms (you may have to tweak the number of loop iterations).
The calculated delta
should always be larger than the printed 'duration' of the loop.
The weird thing is, sometimes it is slower sometimes not. Why?
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/
Please not that the snippet stops after two second.
At least in Blink and Gecko, the timestamp passed to rAF callback is the one of the last VSync pulse.
In the snippet, the CPU and the event-loop are locked for about 300ms, but the monitor still does emit its VSync pulse at the same rate, in parallel.
When the browser is done doing this 300ms computation, it has to schedule a new animation frame.
At the next event-loop iteration it will check if the monitor has sent a new VSync pulse and since it did (about 18 times on a 60Hz), it will execute the new rAF callbacks almost instantly.
The timestamp passed to rAF callback may thus indeed be the one of a time prior to when your last callback ended, because the event-loop got freed after the last VSync pulse.
One way to force this is to make your computation last just a bit more than a frame's duration, for instance on a 60Hz monitor VSync pulses will happen every 16.67ms, so if we lock the event-loop for 16.7ms we are quite sure to have a timestamp delta lesser than the actual computation time:
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">
So what to use between this timestamp and performance.now()
is your call I guess, the timestamp tells you when the frame began, performance.now()
will tell you when your code executes, you could use both if needed. Even without such a big computation spanning over frames, you can very well have an other task scheduled before yours that took a few ms to complete or even a big CSS composition that should get performed after, and you have no real way to know.
这篇关于requestAnimationFrame的时间戳不可靠的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!