两次调用requestAnimationFrame是否正确? 例如:
function animate(){
// ** Animate here
if( something )
requestAnimationFrame(animate);
}
function GameLoop() {
// ** Some code
if ( somethingElse )
animate();
}
setInterval(GameLoop, 1000);
//Main render function
function render(){
// ** Draw and animate objects
requestAnimationFrame(render)
}
render();
我认为这样做是不对的。那么如何将所有动画放入一个RAF中?
答案 0 :(得分:1)
我可以多次调用requestAnimationFrame吗?
是的,可以。 requestAnimationFrame按添加顺序执行功能。
在以下情况下,您应该使用一个requestAnimationFrame循环:
这是一个“饱和的” requestAnimationFrame。
在以下情况下,您可以使用多个requestAnimationFrame循环:
这是一个“不饱和”的requestAnimationFrame。
以在画布上绘制3个圆为例。
在“饱和”代码库中,执行时间很重要。圆圈竞争以获取所需的处理能力。 RAF循环的数量可能是及时完成帧或渲染不完整帧之间的差异。在这种情况下,每秒操作是一个有用的度量。
在我们的“不饱和”代码库中,执行时间无关紧要。圆圈没有竞争以争夺所需的处理能力。在这种情况下,代码无需进一步优化。
完成:JSPerf - RAF Single vs Multiple draw calls Async and Unsaturated
每秒操作数:JSPerf - Single RAF draw calls vs Multiple RAF draw calls
答案 1 :(得分:0)
首先,setInterval和requestAnimation框架都将定期调用动画,每1000毫秒调用一次setInterval,每帧调用requestAnimation。最好只选择两者之一。
使用requestAnimation创建多个循环不一定是错误的,但是大多数人都有一个循环,先调用动画功能再渲染功能。人们会争论性能,但这取决于浏览器如何优化。就个人而言,看到两个不同的循环感觉就像指甲在黑板上刮擦一样,因为我一直见过人们以另一种方式这样做,但是应该可以。
如果您要合并:
function animate(){
// ** Animate here
if( something )
}
//Main render function
function render(){
// ** Draw and animate objects
}
function GameLoop() {
// ** Some code
if ( somethingElse )
animate();
render();
requestAnimationFrame(GameLoop);
}
GameLoop();
答案 2 :(得分:0)
requestAnimationFrame(cb)
做什么?它将推送回调cb
放入动画帧回调列表中,并将文档标记为动画。
然后它将访问此列表,并在单个调用中执行那里的所有回调。
何时发生是基于浏览器的启发式方法,但与绘画框架相对应,这是一个Event Loop,应该在其中渲染标记被引发,并标记了何时启动update the rendering算法。
这意味着所有回调将在浏览器有机会绘制到屏幕之前得到执行。
这也意味着可以,您可以“多次调用requestAnimationFrame”。
requestAnimationFrame(funcA);
requestAnimationFrame(funcB);
// ~ same as
requestAnimationFrame((t)=> {
funcA(t);
funcB(t);
});
它确实将被包装在相同的执行示例中,就像包装在相同的外部函数中一样,因此没有明显的区别(除了抛出的错误不会阻止下一个回调)。
但这并不意味着您应该...
如果有多个不相关的动画需要同时运行,可以这样做,但是对于同一个动画,同时两次调用requestAnimationFrame
通常是一个问题。
很难知道您的回调在堆栈中的何处被添加,从而知道将按什么顺序执行。您可以确定所有开箱即用的操作(来自func = t => requestAnimation(func);
循环的递归调用)将先于来自外部的调用(例如用户事件)先执行。 有关更多信息,请参见this Q/A
因此,如果您从外部获得多个可能的条目,您将不知道哪个会先发生=> 您不知道屏幕上显示的内容。
在您的情况下,解决方案非常简单:
除了在requestAnimationFrame
循环中之外,您不需要从其他任何地方调用render
。
更彻底地分割逻辑:全局循环将先调用更新函数,然后调用绘制函数。
更新功能将负责更新场景中的所有对象,并且仅根据某些外部信号/变量来更新这些对象。
绘图功能将负责绘制所有应渲染的对象,仅此而已。
这些功能都不应该对其他任何事情负责,您的主循环是引擎。
外部事件只会更新场景对象中的某些变量,但永远不会调用绘图函数,它们将不仅仅负责根据需要启动或停止引擎。
const w = canvas.width = 500;
const h = canvas.height = 300;
const ctx = canvas.getContext('2d');
let invertX = false;
const scene = {
objects: [],
update(t) {
// here we only update the objects
this.objects.forEach(obj => {
if(invertX) {
obj.dx *= -1;
}
obj.x += obj.dx;
obj.y += obj.dy;
if(obj.x > w) obj.x = (obj.x - w) - obj.w;
if(obj.x + obj.w < 0) obj.x = w - (obj.x + obj.w);
if(obj.y > h) obj.y = (obj.y - h) - obj.h;
if(obj.y + obj.h < 0) obj.y = h - (obj.y + obj.h);
});
invertX = false;
},
draw() {
// here we only draw
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width, canvas.height);
this.objects.forEach(obj => {
ctx.fillStyle = obj.fill;
ctx.fillRect(obj.x, obj.y, obj.w, obj.h);
});
}
}
function mainLoop() {
scene.update();
scene.draw();
requestAnimationFrame(mainLoop);
}
for(let i=0; i<50; i++) {
scene.objects.push({
x: Math.random() * w,
y: Math.random() * h,
w: Math.random() * w / 5,
h: Math.random() * h / 5,
dx: (Math.random() * 3 - 1.5),
dy: (Math.random() * 3 - 1.5),
fill: '#' + Math.floor(Math.random()*0xFFFFFF).toString(16)
});
}
// every second do something external
setInterval(() => {
invertX = true;
}, 1000);
// make one follow the cursor
onmousemove = e => {
const evtX = e.clientX - canvas.offsetLeft;
const evtY = e.clientY - canvas.offsetTop;
const obj = scene.objects[0];
const dirX = Math.sign(evtX - obj.x);
const dirY = Math.sign(evtY - obj.y);
obj.dx = Math.abs(obj.dx) * dirX;
obj.dy = Math.abs(obj.dy) * dirY;
}
mainLoop();
<canvas id="canvas"></canvas>