我开始学习这种方法requestAnimationFrame
,但我发现它有点难以理解(至少对我而言)。我知道setInterval / setTimeout是如何工作的,但是当我尝试输入一些简单的代码来测试时:
function message(){console.log('hello');}
requestAnimationFrame(message);
这不会使控制台中的任何动画显示hello消息只出现一次,但是当我在消息函数中调用requestAnmiationFrame(message)
时,循环是无穷大的(因此它正在工作)。
但另一方面,我可以使用setInterval / setTimeout在任何地方调用此消息函数,而不必在消息函数内部。
我可以理解,requestAnimationFrame()
方法必须在函数内运行吗?或者我应该说在内部使用requestAnimiationFrame()
方法时,有一个函数有一个默认的循环用于循环自身的无限时间吗?
问题可能是愚蠢的,但真的希望有人能够启发我。
答案 0 :(得分:1)
rAF
(请求动画帧)和timers
(setTimeout
/ setInterval
)之间存在巨大差异。
两者均创建异步运行的任务(您的回调),即浏览器将它们排队并在特定时间点调用它们。
最大的区别在于它们何时获得执行和优先级。
我们需要了解谁在管理Que,以及在什么条件下允许回调运行。
我会尽量保持简单。
JavaScript是“单线程”的,这意味着您无法在纯JavaScript中运行异步操作。
浏览器API为您提供了一些方法来异步运行代码:
timers
rAF
DOM
个事件promises
(这是另一种任务)xhr
使用这些方法之一时,实际上是将一个函数引用传递给浏览器,以在(希望的)所需时间点调用它。
浏览器将根据您使用的API来执行此操作,并在任务Que中对您的回调进行队列化处理(承诺除外,这些都是微任务)。
最大的问题是,何时将其传递回“ javascript-land”到调用堆栈中?
这是事件循环的工作,它将检查2个条件:
如果满足这两个条件,它将从Que中提取下一个任务并将其推入调用堆栈。
现在让我们以setTimeout
为例。
给出以下代码:
setTimeout(() => console.log('timeout'), 0);
console.log('start');
console.log('end');
setTimout
应该等待0ms
然后登录timeout
,因此我们应该认为日志顺序为:
timeout
start
end
但实际上是:
start
end
timeout
原因是因为我们将回调传递给了浏览器,浏览器等待0ms
然后将其推送到任务que。
现在,事件循环的工作就是检查上述两个条件:
两者的答案都是“否”,因为我们仍有2条console.log
行要在主要执行上下文上运行。
因此它等待,当我们完成日志时,它将回调从que传递到调用堆栈,然后我们记录timeout
。
大多数异步回调都以这种方式工作。
请注意,promises
是不同的,它们是微任务,具有自己的Que,其优先级高于常规回调任务。
好的,所有这些谈话,我还没有回答您的问题。
在回答之前还有一件事。
是另一个浏览器API,可让您在浏览器即将运行渲染步骤之前运行内容:
因此,例如,如果要更新下一帧上元素的left
位置,则可以在此位置进行。
这是可能的实现:
requestAnimationFrame(moveBox)
如果您希望它在每一帧上都保持移动,则应递归调用它:
function callback() {
moveBox();
requestAnimationFrame(callback);
}
callback();
rAF
上使用timers
?setInterval(moveBox, 0)
这将比rAF
实现更快。
它运行得更快的原因是,在浏览器将有机会重新绘制(处理渲染步骤)之前,回调将被多次调用,因此您将多次“更新” left
位置,但是浏览器只会重新绘制您传递的最后一个值。
例如,它将从0px
跳到15px
,而不是一次增加一个像素。
基本上,您可以做的是“猜测”帧之间将流逝多少时间。 最常见的方法是计算“每秒60帧”,因为大多数屏幕都支持该比率。
setInterval(moveBox2, 1000 / 60)
但是,即使这并不是平滑或一致的,因为它并不是真正的60 fps,最重要的是,当前设备和屏幕的刷新速度可能不如您想象的那样。
因此,用于处理DOM动画,批处理更新和评估内容的最佳API是requestAnimationFrame
。
我举了一个用rAF
和setInterval
的2个解决方案移动盒子的小例子,希望对您有所帮助。
const showBox2 = location.search.includes('box2');
const showBox3 = location.search.includes('box3');
const box1 = document.getElementById('box1');
const box2 = document.getElementById('box2');
const box3 = document.getElementById('box3');
// move by 1 px
function move1px(el) {
let left = 0;
return function() {
// infinite looping logic - if off screen start over
if (document.documentElement.clientWidth < left) {
left = 0;
} else {
left++;
}
// move left by 1 px
el.style.left = left + 'px';
}
}
// raf solution
const moveBox1 = move1px(box1);
function callback() {
moveBox1();
requestAnimationFrame(callback);
}
callback();
// setInterval solution
const moveBox2 = move1px(box2);
setInterval(moveBox2, 0);
// setInterval hack
const moveBox3 = move1px(box3);
setInterval(moveBox3, 1000 / 60);
body {
overflow: hidden;
}
#root {
display: flex;
flex-direction: column;
}
.box {
display: flex;
justify-content: center;
align-items: center;
position: relative;
height: 30px;
width: 150px;
border: 1px solid #333;
margin: 10px 0;
color: #fff;
font-size: 1em;
}
#box1 {
background-color: slateblue;
}
#box2 {
background-color: brown;
}
#box3 {
background-color: green;
}
<div id="root">
<div class="box" id="box3">setInterval hack</div>
<div class="box" id="box1">rAF</div>
<div class="box" id="box2">setInterval</div>
</div>
答案 1 :(得分:0)
requestAnimationFrame实际上并不为您呈现任何内容,它只是将浏览器的呈现循环与您的代码同步,因此您可以使用它以编程方式在屏幕上呈现动画。
它的作用是触发每次浏览器渲染帧时提供的回调函数。这是一个很好的例子,它用于:javascript game