Могу ли я вызвать requestAnimationFrame несколько раз? - PullRequest
0 голосов
/ 06 февраля 2019

Правильно ли вызывать requestAnimationFrame 2 или более раз?Например:

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?

Ответы [ 3 ]

0 голосов
/ 06 февраля 2019

Могу ли я вызывать requestAnimationFrame несколько раз?

Да, вы можете.requestAnimationFrame выполняет функции в порядке сложения.

Вы должны использовать один цикл requestAnimationFrame, если:

  • Производительность - самая важная вещь в вашем приложении.
  • Если ваше приложениесбрасывает кадры (или вы боитесь, что это может произойти в какой-то момент вашей дорожной карты).

Это «насыщенный» requestAnimationFrame.

Вы можете использовать более одного цикла requestAnimationFrame, если:

  • Ваши задачи просты.
  • Вы доверяете полностью выполнить свои задачи перед следующим кадром.

Это ненасыщенный requestAnimationFrame.

Возьмите, например, задачу рисования 3 кругов на холсте..

В «насыщенной» кодовой базе время выполнения имеет значение.Круги конкурируют за требуемую вычислительную мощность.Количество циклов RAF может быть разницей между завершением кадра во времени или рендерингом неполного кадра.В этом случае полезными являются операции в секунду.

В нашей «ненасыщенной» кодовой базе время выполнения не имеет значения.Круги не конкурируют за требуемую вычислительную мощность.В этом случае код не требует дальнейшей оптимизации.

Завершение: JSPerf - RAF Один или несколько вызовов отрисовки Асинхронное и Ненасыщенное

Операций в секунду: JSPerf- Одиночные вызовы отрисовки RAF против нескольких вызовов отрисовки RAF

0 голосов
/ 07 февраля 2019

Что делает 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>
0 голосов
/ 06 февраля 2019

Прежде всего, и setInterval, и кадр requestAnimation будут регулярно вызывать animate, setInterval каждые 1000 мс и 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();
...