报告渲染性能标记时,requestAnimationFrame(rAF)的用途是什么



在我们的代码库中,我们使用RAFTimer来报告渲染性能标记。代码如下所示,callback将发送渲染性能标记。我对这里为什么需要requestAnimationFrame(rAF(感到困惑。setTimeout将在同步码(呈现逻辑(之后执行,我认为setTimeout的时机正好。用rAF包装的目的是什么?

export function rafTimer(callback: Function): void {
requestAnimationFrame(() =>
setTimeout(() => callback(), 0)
);
}

requestAnimationFrame(cb)cb安排为在下一个页面渲染之前调用
因此,在执行回调时,渲染尚未完成
但是,从rAF回调调度0ms超时确实允许在渲染发生后激发函数。

要测量渲染实际花费的时间,确实需要获得渲染发生前后的时间。

请注意,过去有一个requestPostAnimationFrame提案,这样我们就可以在事件循环中的这个确切位置使用原生挂钩,但在过去几年里,它似乎没有太大变化,所以在得到广泛支持之前,我不会屏住呼吸。但您仍然可以使用我为另一个SO Q/A制作的polyfill,它确实使用了比setTimeout更快的任务调度,因此我们更有信心从渲染步骤开始就尽可能接近。

此外,为了确保您的rAF回调尽可能晚地启动,您需要从postAnimationFrame回调(即setTimeout中的回调(中安排它,以便在其间尽可能少地启动回调。直接从rAF回调调度它,您可以在回调后堆叠其他rAFAF回调,例如,如果它们是从单击事件中安排的。

// requestPostAnimationFrame polyfill
// from https://stackoverflow.com/a/57549862/3702797
"function"!=typeof requestPostAnimationFrame&&(()=>{const a=new MessageChannel,b=[];let c=0,d=!1,e=!1,f=!1;a.port2.onmessage=()=>{d=!1;const a=b.slice();b.length=0,a.forEach(a=>{try{a(c)}catch(a){}})};const g=globalThis.requestAnimationFrame;globalThis.requestAnimationFrame=function(...a){e||(e=!0,g.call(globalThis,a=>f=a),globalThis.requestPostAnimationFrame(()=>{e=!1,f=!1})),g.apply(globalThis,a)},globalThis.requestPostAnimationFrame=function(e){if("function"!=typeof e)throw new TypeError("Argument 1 is not callable");b.push(e),d||(f?(c=f,a.port1.postMessage("")):requestAnimationFrame(b=>{c=b,a.port1.postMessage("")}),d=!0)}})();
// measure as close as possible the time it took for rendering the page
function measureRenderingTime(cb) {
let t1, t2;
const rAFCallback = () => {
requestPostAnimationFrame(rPAFCallback);
t1 = performance.now(); // before rendering
};
const rPAFCallback = () => {
t2 = performance.now(); // after rendering
cb(t2 - t1);
};
requestAnimationFrame(rAFCallback);
}
// do something with the measurements
// (here simple max + average over 50 occurrences)
const times = [];
const handleRenderingTime = (time) => {
times.push(time);
while (times.length > 50) {
times.shift();
}
document.querySelector("pre").textContent = `last: ${ time.toFixed(2) }
max:  ${ Math.max(...times).toFixed(2) }
avg:  ${ (times.reduce((t,v) => t+v, 0) / times.length).toFixed(2) }`;
// loop
measureRenderingTime(handleRenderingTime);
};
measureRenderingTime(handleRenderingTime); // begin the loop
// simulate an actual rAF loop, with sometimes long JS execution time
let longFrame = false;
document.querySelector("button").onclick = (evt) => longFrame = true;
const animLoop = () => {
if (longFrame) {
const t1 = performance.now();
// lock the browser for 300ms
// this is actually only JS, that shouldn't impact the rendering time by much
while (performance.now() - t1 < 300) {}
};
longFrame = false;
requestAnimationFrame(animLoop);
};
requestAnimationFrame(animLoop);
<pre id=log></pre>
<button>perform a long rAF callback</button>

最新更新