javascript - 关于帧丢失和 requestAnimationFrame 的困惑
请随时指出如果我的以下理解有错误:假设我们的显示器刷新率是60Hz(我知道实际情况并不总是如此,但我们就假设是60Hz),那么网页每秒会刷新屏幕60次,如果一切顺利的话。这意味着渲染大约以16毫秒的间隔发生,对吗?所以在我们的JavaScript中,任何执行时间超过16毫秒的操作都会导致用户体验卡顿。所以我的问题是:
- 假设我们有一个函数
handleScroll,它从开始到结束需要 100 毫秒来执行。然后我们将它添加到addEventListener('scroll', handleScroll)。是否可以说,每当scroll事件触发时,用户会体验到卡顿,因为在渲染周期中会跳过/丢失 6 帧?因为 100ms/16ms=6.25?我知道主线程上的任务如果执行时间很长,会阻塞所有其他任务直到完成,但在这里我想获得一些对这种性能问题的 量化分析方法,或者用于定性分析的方法。具体来说,我想大致了解在这种回调下(如果刷新率是 60Hz),会丢失多少帧。 - 我认为
requestAnimationFrame告诉浏览器在下一个帧渲染之前运行回调,所以我看到有人提到它可以防止动画掉帧。但我不清楚它是如何做到这一点的,因为我们传入requestAnimationFrame的回调仍然会执行完毕,如果这个回调耗时超过16毫秒,我们不可避免地会错过下一帧,对吧?
答案#1
是的,在那种情况下,你触发
handleScroll的频率会远低于 60 帧每秒——取决于你的handleScroll回调在做什么,你的用户可能会体验到一些卡顿。requestAnimationFrame会尽力保持 60 帧每秒,但不保证 60 帧每秒。它可能会根据可用的 CPU、GPU、内存和其他限制运行得慢得多。
请注意,即使它以超过60帧每秒运行,这也会给你(正如你指出的)一个16-17毫秒的帧预算来执行你的回调操作。
所以如果你的回调函数执行需要 100 毫秒,那么即使使用 requestAnimationFrame,你也无法获得流畅的 60fps 动画。Chrome 的性能开发工具可以帮助你找出导致动画延迟的原因,但你需要自己优化回调函数,使其运行时间少于 17 毫秒,以防止掉帧。
查看这篇文章以获得更深入的分析
答案#2
你的假设有两个警告,首先是你有16毫秒的预算(在60赫兹下)可以使用,这是不正确的,因为浏览器必须进行某种内部计算来绘制下一帧,这在 Chrome 上大约需要 6 毫秒,因此正如这里解释的,我们大约只有 ~10 毫秒的时间。
第二个假设是设备将具有60Hz的刷新率,这在不久的将来将会过时,因为越来越多的设备使用更高的刷新率来提高滚动的流畅度,或者甚至降低刷新率以节省电池;因此这些是假设不安全的。
顺便说一下,原则通常是相同的,如果一个 任务在主线程上花费很长时间,它 将停止所有其他任务 直到完成,让我们演示它的实际效果:
lag 函数模拟一个需要一段时间才能运行的 CPU 密集型任务;raf 函数将通过向自身调度递归的 requestAnimationFrame 来移动 200px,这将改变盒子的 translateX 属性;最后我们有一个 laggyRaf,它使用 lag 函数来模拟一个耗时的任务;
const box = document.querySelector('.box');
const x_move_distance = 200;
function lag (delay = 1000) {
const time = Date.now();
while ( Date.now() < time + delay ) {
// waits
}
}
function moveBox ( position ) {
box.style.transform = `translateX(${position}px)`;
}
let counter = 0;
function raf() {
moveBox(counter);
if ( counter < x_move_distance ) {
requestAnimationFrame(raf);
counter++;
}
}
let counter2 = 0;
function laggyRaf() {
moveBox(counter2);
lag(100); //100 ms seconds extra lag
if ( counter2 < x_move_distance ) {
requestAnimationFrame(laggyRaf);
counter2++;
}
}.box {
width: 100px;
height: 100px;
background: blue;
}<div class='box'></div>
<button onclick="counter = 0; raf()">start raf animations</button>
<button onclick="lag()">start cpu-intensive task</button>
<br />
<button onclick="counter2 = 0; laggyRaf()">start laggy animations</button>