requestAnimationFrame 在函数的开头或结尾?
requestAnimationFrame at beginning or end of function?
如果我有一个像这样使用 requestAnimationFrame 的循环:
function render() {
// Rendering code
requestAnimationFrame(render);
}
如果我把requestAnimationFrame
放在函数的开头会有什么不同吗,像这样:
function render() {
requestAnimationFrame(render);
// Rendering code
}
我没有注意到任何区别,但我已经看到了两种实现方式,其中一种在任何方面都更好,还是相同?
编辑:
我想过的一件事是,如果我把它放在开头,渲染代码需要很长时间才能达到 运行,比如说 10ms,那么把它放在最后不会让帧率下降 10ms 吗?
requestAnimationFrame
确实总是异步调用其回调,因此只要您的渲染代码是同步的并且不抛出异常,它就没有任何区别。
这本质上是一种风格选择,你自己选择哪种方法更干净。将它放在顶部可能会强调 render
正在调度自身,即使在呈现错误的情况下也会这样做。将它放在底部可以有条件地跳出渲染循环(例如,当您想暂停游戏时)。
MDN description 指出:
The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint.
何时进行重绘主要取决于浏览器。除非您的 JS 在重绘发生时仍然 运行,否则行为应该没有任何差异。
The WhatWG spec 没有提到等待 JS 调用堆栈清除或任何类似的事情,尽管一个特别长的 运行 函数会阻塞 UI 线程,因此应该防止调用动画帧。
这可能不会有什么不同。 requestAnimationFrame
方法是异步的,因此无论哪种方式,您的渲染函数都会按预期工作。但是......在停止时有一个陷阱。假设您有以下代码:
function render() {
requestAnimationFrame(render);
// Rendering code
}
为了停止下一次渲染,需要调用 cancelAnimationFrame
方法,如下所示:
function render() {
requestAnimationFrame(render);
// Rendering code
if (noLongerInterested) {
cancelAnimationFrame();
}
}
否则,render
方法将无限期地 运行。或者,您可以这样做:
function render() {
// Rendering code
if (stillInterested) {
requestAnimationFrame(render);
}
}
至于丢帧,您可以将 requestAnimationFrame
视为固定时间表(每秒 60 帧,间隔大约为 16 毫秒)。如果您的代码花费的时间超过该时间,浏览器将开始丢帧。查看 了解有关如何管理帧的说明,并使用它来实现更一致的渲染。
希望对您有所帮助!
为了回答您的问题,只有当您的渲染代码长于动画帧速度(通常约为 16 - 33 毫秒,具体取决于浏览器实现)时,这两个函数才会对发生异步回调所需的时间产生影响).但是,如果您按预期使用此 API,即使那样也不会有什么不同。
请注意,您选择不使用从 requestAnimationFrame
-- the timestamp
传递的可选参数。
如果您要渲染任何增量时间相关的动画,请务必计算增量。通常,您将动画 "velocity" 与 timestamp
增量相乘(当前 timestamp
减去之前的 timestamp
)以获得对象应穿过屏幕的有效距离。当您的渲染代码执行每一帧所花费的时间不一致时,其效果尤为明显。
演示
var untimed = 20;
var timed = 20;
function untimedRender() {
var then = performance.now() + Math.random() * 100;
while (performance.now() < then) {}
// estimated velocity
untimed += 50 / 30;
document.querySelector('#untimed').style.left = Math.min(Math.floor(untimed), 200) + 'px';
if (untimed < 200) {
requestAnimationFrame(untimedRender);
} else {
last = performance.now();
requestAnimationFrame(timedRender);
}
}
var last;
function timedRender(timestamp) {
var delta = timestamp - last;
var then = timestamp + Math.random() * 100;
last = timestamp;
while (performance.now() < then) {}
// calculated velocity
timed += delta / 30;
document.querySelector('#timed').style.left = Math.min(Math.floor(timed), 200) + 'px';
if (timed < 200) {
requestAnimationFrame(timedRender);
}
}
requestAnimationFrame(untimedRender);
div {
position: absolute;
left: 20px;
width: 10px;
height: 10px;
}
#untimed {
background-color: #F00;
top: 20px;
}
#timed {
background-color: #00F;
top: 50px;
}
<div id="untimed"></div>
<div id="timed"></div>
注意蓝色方块是如何保持整体速度更一致的。就是这个意思。
如果我有一个像这样使用 requestAnimationFrame 的循环:
function render() {
// Rendering code
requestAnimationFrame(render);
}
如果我把requestAnimationFrame
放在函数的开头会有什么不同吗,像这样:
function render() {
requestAnimationFrame(render);
// Rendering code
}
我没有注意到任何区别,但我已经看到了两种实现方式,其中一种在任何方面都更好,还是相同?
编辑: 我想过的一件事是,如果我把它放在开头,渲染代码需要很长时间才能达到 运行,比如说 10ms,那么把它放在最后不会让帧率下降 10ms 吗?
requestAnimationFrame
确实总是异步调用其回调,因此只要您的渲染代码是同步的并且不抛出异常,它就没有任何区别。
这本质上是一种风格选择,你自己选择哪种方法更干净。将它放在顶部可能会强调 render
正在调度自身,即使在呈现错误的情况下也会这样做。将它放在底部可以有条件地跳出渲染循环(例如,当您想暂停游戏时)。
MDN description 指出:
The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint.
何时进行重绘主要取决于浏览器。除非您的 JS 在重绘发生时仍然 运行,否则行为应该没有任何差异。
The WhatWG spec 没有提到等待 JS 调用堆栈清除或任何类似的事情,尽管一个特别长的 运行 函数会阻塞 UI 线程,因此应该防止调用动画帧。
这可能不会有什么不同。 requestAnimationFrame
方法是异步的,因此无论哪种方式,您的渲染函数都会按预期工作。但是......在停止时有一个陷阱。假设您有以下代码:
function render() {
requestAnimationFrame(render);
// Rendering code
}
为了停止下一次渲染,需要调用 cancelAnimationFrame
方法,如下所示:
function render() {
requestAnimationFrame(render);
// Rendering code
if (noLongerInterested) {
cancelAnimationFrame();
}
}
否则,render
方法将无限期地 运行。或者,您可以这样做:
function render() {
// Rendering code
if (stillInterested) {
requestAnimationFrame(render);
}
}
至于丢帧,您可以将 requestAnimationFrame
视为固定时间表(每秒 60 帧,间隔大约为 16 毫秒)。如果您的代码花费的时间超过该时间,浏览器将开始丢帧。查看
希望对您有所帮助!
为了回答您的问题,只有当您的渲染代码长于动画帧速度(通常约为 16 - 33 毫秒,具体取决于浏览器实现)时,这两个函数才会对发生异步回调所需的时间产生影响).但是,如果您按预期使用此 API,即使那样也不会有什么不同。
请注意,您选择不使用从 requestAnimationFrame
-- the timestamp
传递的可选参数。
如果您要渲染任何增量时间相关的动画,请务必计算增量。通常,您将动画 "velocity" 与 timestamp
增量相乘(当前 timestamp
减去之前的 timestamp
)以获得对象应穿过屏幕的有效距离。当您的渲染代码执行每一帧所花费的时间不一致时,其效果尤为明显。
演示
var untimed = 20;
var timed = 20;
function untimedRender() {
var then = performance.now() + Math.random() * 100;
while (performance.now() < then) {}
// estimated velocity
untimed += 50 / 30;
document.querySelector('#untimed').style.left = Math.min(Math.floor(untimed), 200) + 'px';
if (untimed < 200) {
requestAnimationFrame(untimedRender);
} else {
last = performance.now();
requestAnimationFrame(timedRender);
}
}
var last;
function timedRender(timestamp) {
var delta = timestamp - last;
var then = timestamp + Math.random() * 100;
last = timestamp;
while (performance.now() < then) {}
// calculated velocity
timed += delta / 30;
document.querySelector('#timed').style.left = Math.min(Math.floor(timed), 200) + 'px';
if (timed < 200) {
requestAnimationFrame(timedRender);
}
}
requestAnimationFrame(untimedRender);
div {
position: absolute;
left: 20px;
width: 10px;
height: 10px;
}
#untimed {
background-color: #F00;
top: 20px;
}
#timed {
background-color: #00F;
top: 50px;
}
<div id="untimed"></div>
<div id="timed"></div>
注意蓝色方块是如何保持整体速度更一致的。就是这个意思。