Что делает requestAnimationFrame(cb)
?
Он помещает обратный вызов cb
в список обратных вызовов кадра анимации и помечает документ как анимированный .
Затем он посетит этот список и выполнит все обратные вызовы там за один вызов., когда это происходит, основано на эвристике браузера, но соответствует рамке рисования , которая представляет собой цикл обработки событий , где должен отображать флаг был поднят и показывает, когда обновляет алгоритм рендеринга .
Это означает, что все обратные вызовы будут выполнены до того, как браузер получит возможность рисовать вэкран.
Это также означает, что да, вы можете "вызывать requestAnimationFrame несколько раз".
requestAnimationFrame(funcA);
requestAnimationFrame(funcB);
// ~ same as
requestAnimationFrame((t)=> {
funcA(t);
funcB(t);
});
Он действительно будет заключен в тот же пример выполнения, как если бы он был заключен в ту же внешнюю функцию, без видимой разницы (за исключением того, что сгенерированная ошибка не заблокирует следующие обратные вызовы).
Но это не значит, что вы должны ...
Это нормально, если у вас есть несколько несвязанных анимаций, которые должны выполняться одновременно, однако, для одного и того жеанимация с двумя одновременными вызовами на requestAnimationFrame
обычно является проблемой.
Очень трудно узнать, где в стеке обратных вызовов был добавлен ваш и, следовательно, в каком порядке он будет выполнен.Все, что вы можете быть уверены из коробки - это то, что все рекурсивные вызовы (из цикла func = t => requestAnimation(func);
) будут выполнены до , поступающие извне (например, пользовательские события). Подробнее об этом см. этот вопрос / ответ
Так что, если у вас когда-либо будет более одной возможной записи извне, вы не сможете знать, какая из них будетслучись первым => Вы не знаете, что у вас на экране .
Решение довольно простое в вашем случае:
Вам не нужно звонить 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>