setTimeout 与 requestAnimationFrame
setTimeout vs requestAnimationFrame
我对“setTimeout
”与“requestAnimationFrame
”进行了 example 比较,以了解它们有何不同。
如您所见,橙色盒子最先到达目的地。绿框跳动了几次,速度变慢了。
我明白了为什么绿框有时会跳动。因为任务(调用移动函数)在重绘之前不会插入到 macroTaskQueue 中(这称为卡顿或跳帧)。
这就是为什么我更喜欢 requestAnimationFrame
而不是 setTimeout
的原因。因为 requestAnimationFrame(move)
的 move()
保证在重绘之前被调用。
现在,我想知道的是 为什么绿色框比橙色框慢
这是否意味着 move()
不是每 1000/60 毫秒调用一次?
setTimeout
总是迟到。
它的工作方式是
- 注册执行任务的时间戳。
- 在每个事件循环的迭代中,检查 now 是否在该时间戳之后。
- 执行任务。
通过这种设计,setTimeout()
被迫花费 至少 由 延迟 定义的时间量。它可以(并且经常)更多,例如,如果事件循环忙于做其他事情(比如处理用户手势,调用垃圾收集器等)。
现在,由于您仅在调用前一个回调时请求新的超时,因此您的 setTimeout()
循环受到 time-drift 的影响。每次迭代都会积累这种漂移,并且永远无法从中恢复,远离 wall-clock 时间。
另一方面,requestAnimationFrame
(rAF) 不会出现这种漂移。实际上,监视器的 V-Sync 信号告诉事件循环何时必须进入“update the rendering”步骤。此信号未绑定到 CPU activity 并将用作稳定时钟。如果在一帧 rAF 回调延迟了几毫秒,下一帧之间的时间就会减少,但标志将以固定的间隔设置而不会漂移。
您可以通过提前安排所有计时器来验证这一点,您的 setTimeout 框将不再受此影响:
const startBtn = document.querySelector('#a');
const jankBtn = document.querySelector('#b');
const settimeoutBox = document.querySelector('.settimeout-box');
const requestAnimationFrameBox = document.querySelector('.request-animation-frame-box');
settimeoutBox._left = requestAnimationFrameBox._left = 0;
let i = 0;
startBtn.addEventListener('click', () => {
startBtn.classList.add('loading');
startBtn.classList.add('disabled');
scheduleAllTimeouts(settimeoutBox);
moveWithRequestAnimationFrame(requestAnimationFrameBox);
});
function reset() {
setTimeout(() => {
startBtn.classList.remove('loading');
startBtn.classList.remove('disabled');
i = 0;
settimeoutBox.style.left = '0px';
requestAnimationFrameBox.style.left = '0px';
settimeoutBox._left = requestAnimationFrameBox._left = 0;
}, 300);
}
function move(el) {
el._left += 2;
el.style.left = el._left + 'px';
if (el._left > 1000) {
return false;
}
return true;
}
function scheduleAllTimeouts(el) {
for (let i = 0; i < 500; i++) {
setTimeout(() => move(el), i * 1000 / 60);
}
}
function moveWithRequestAnimationFrame(el) {
if (move(el)) {
requestAnimationFrame(() => {
moveWithRequestAnimationFrame(el);
});
} else reset();
}
.grid {
margin: 30px !important;
padding: 30px;
}
.box {
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
color: white;
font-size: 18px;
}
.settimeout-box {
background-color: green;
}
.request-animation-frame-box {
background-color: orange;
}
<div class="ui grid container">
<div class="row">
<button class="ui button huge blue" id="a">Start!</button>
</div>
<div class="row">
<div class="box settimeout-box">
<span>setTimeout</span>
</div>
</div>
<div class="row">
<div class="box request-animation-frame-box">
<span>requestAnimationFrame</span>
</div>
</div>
</div>
请注意,Firefox 和 Chrome 实际上会在 non-animated 文档中第一次调用 rAF 后立即触发绘制帧,因此 rAF 可能比 setTimeout 早一帧 在这个演示中。
requestAnimationFrame
的频率是相对于显示器的refresh-rate.
以上示例假设您 运行 它在 60Hz 显示器上。具有更高或更低刷新率的显示器将以不同的频率进入此“更新渲染”步骤。
另请注意,setTimeout(fn, delay)
中的 delay
是 long
,这意味着您传递的值将被取整数。
最后一点,Chrome 确实在其 setInteval()
实现中自我纠正了这次漂移,Firefox 和规范仍然没有,但它是 under (not so active) discussion。
我对“setTimeout
”与“requestAnimationFrame
”进行了 example 比较,以了解它们有何不同。
如您所见,橙色盒子最先到达目的地。绿框跳动了几次,速度变慢了。
我明白了为什么绿框有时会跳动。因为任务(调用移动函数)在重绘之前不会插入到 macroTaskQueue 中(这称为卡顿或跳帧)。
这就是为什么我更喜欢 requestAnimationFrame
而不是 setTimeout
的原因。因为 requestAnimationFrame(move)
的 move()
保证在重绘之前被调用。
现在,我想知道的是 为什么绿色框比橙色框慢
这是否意味着 move()
不是每 1000/60 毫秒调用一次?
setTimeout
总是迟到。
它的工作方式是
- 注册执行任务的时间戳。
- 在每个事件循环的迭代中,检查 now 是否在该时间戳之后。
- 执行任务。
通过这种设计,setTimeout()
被迫花费 至少 由 延迟 定义的时间量。它可以(并且经常)更多,例如,如果事件循环忙于做其他事情(比如处理用户手势,调用垃圾收集器等)。
现在,由于您仅在调用前一个回调时请求新的超时,因此您的 setTimeout()
循环受到 time-drift 的影响。每次迭代都会积累这种漂移,并且永远无法从中恢复,远离 wall-clock 时间。
requestAnimationFrame
(rAF) 不会出现这种漂移。实际上,监视器的 V-Sync 信号告诉事件循环何时必须进入“update the rendering”步骤。此信号未绑定到 CPU activity 并将用作稳定时钟。如果在一帧 rAF 回调延迟了几毫秒,下一帧之间的时间就会减少,但标志将以固定的间隔设置而不会漂移。
您可以通过提前安排所有计时器来验证这一点,您的 setTimeout 框将不再受此影响:
const startBtn = document.querySelector('#a');
const jankBtn = document.querySelector('#b');
const settimeoutBox = document.querySelector('.settimeout-box');
const requestAnimationFrameBox = document.querySelector('.request-animation-frame-box');
settimeoutBox._left = requestAnimationFrameBox._left = 0;
let i = 0;
startBtn.addEventListener('click', () => {
startBtn.classList.add('loading');
startBtn.classList.add('disabled');
scheduleAllTimeouts(settimeoutBox);
moveWithRequestAnimationFrame(requestAnimationFrameBox);
});
function reset() {
setTimeout(() => {
startBtn.classList.remove('loading');
startBtn.classList.remove('disabled');
i = 0;
settimeoutBox.style.left = '0px';
requestAnimationFrameBox.style.left = '0px';
settimeoutBox._left = requestAnimationFrameBox._left = 0;
}, 300);
}
function move(el) {
el._left += 2;
el.style.left = el._left + 'px';
if (el._left > 1000) {
return false;
}
return true;
}
function scheduleAllTimeouts(el) {
for (let i = 0; i < 500; i++) {
setTimeout(() => move(el), i * 1000 / 60);
}
}
function moveWithRequestAnimationFrame(el) {
if (move(el)) {
requestAnimationFrame(() => {
moveWithRequestAnimationFrame(el);
});
} else reset();
}
.grid {
margin: 30px !important;
padding: 30px;
}
.box {
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
color: white;
font-size: 18px;
}
.settimeout-box {
background-color: green;
}
.request-animation-frame-box {
background-color: orange;
}
<div class="ui grid container">
<div class="row">
<button class="ui button huge blue" id="a">Start!</button>
</div>
<div class="row">
<div class="box settimeout-box">
<span>setTimeout</span>
</div>
</div>
<div class="row">
<div class="box request-animation-frame-box">
<span>requestAnimationFrame</span>
</div>
</div>
</div>
请注意,Firefox 和 Chrome 实际上会在 non-animated 文档中第一次调用 rAF 后立即触发绘制帧,因此 rAF 可能比 setTimeout 早一帧 在这个演示中。
requestAnimationFrame
的频率是相对于显示器的refresh-rate.
以上示例假设您 运行 它在 60Hz 显示器上。具有更高或更低刷新率的显示器将以不同的频率进入此“更新渲染”步骤。
另请注意,setTimeout(fn, delay)
中的 delay
是 long
,这意味着您传递的值将被取整数。
最后一点,Chrome 确实在其 setInteval()
实现中自我纠正了这次漂移,Firefox 和规范仍然没有,但它是 under (not so active) discussion。