When will requestAnimationFrame be executed?

耗尽温柔 提交于 2019-12-03 06:11:12

It's basically its own thing. When the browser is about to repaint the page, which it does typically 60 times/second if not blocked by a running task, it will call all queued requestAnimationFrame callbacks just before doing so, and then do the repaint.

The spec doesn't say whether that can happen between the completion of a macrotask and the processing of its scheduled microtasks, or only between macrotasks. So presumably that can vary browser to browser.

The old spec (now obsolete and superceded) described it in macrotask terms, suggesting it would be between macrotasks, but things may have moved on from there.

Let's do a test:

const messages = [];
setTimeout(() => {
  // Schedule a microtask
  Promise.resolve().then(() => {
    log("microtask");
  });
  
  // Schedule animation frame callback
  requestAnimationFrame(() => {
    log("requestAnimationFrame");
  });
  
  // Schedule a macrotask
  setTimeout(() => {
    log("macrotask");
  }, 0);
  
  // Schedule a callback to dump the messages
  setTimeout(() => {
    messages.forEach(msg => {
      console.log(msg);
    });
  }, 200);

  // Busy-wait for a 10th of a second; the browser will be eager to repaint when this task completes
  const stop = Date.now() + 100;
  while (Date.now() < stop) {
  }
  
}, 100);

function log(msg) {
  messages.push(Date.now() + ": " + msg);
}

Sure enough, the results vary by browser:

  • Chrome: microtask, requestAnimationFrame, macrotask
  • Firefox: microtask, macrotask, requestAnimationFrame

(I reliably get the same results in repeated tests on those browsers. I don't have Edge handy...)

Here's a version with the busy-wait up front, instead of at the end, in case it changes something:

const messages = [];
setTimeout(() => {
  // Busy-wait for a 10th of a second; the browser will be eager to repaint when this task completes
  const stop = Date.now() + 100;
  while (Date.now() < stop) {
  }
  
  // Schedule a microtask
  Promise.resolve().then(() => {
    log("microtask");
  });
  
  // Schedule animation frame callback
  requestAnimationFrame(() => {
    log("requestAnimationFrame");
  });
  
  // Schedule a macrotask
  setTimeout(() => {
    log("macrotask");
  }, 0);
  
  // Schedule a callback to dump the messages
  setTimeout(() => {
    messages.forEach(msg => {
      console.log(msg);
    });
  }, 200);

}, 100);

function log(msg) {
  messages.push(Date.now() + ": " + msg);
}

I reliably get microtask, requestAnimationFrame, macrotask on both Chrome and Firefox with that change.


**Q1: **

Even though the browser has no repaint work, the requestAnimationFrame's callback will be excuted at the refresh rate (default 60 per second).

Provided nothing is blocking.

**Q2: **

That sentence means exactly, and only, what it says: Your callback will be called (along with any requestAnimationFrame callbacks that are queued up) immediately prior to drawing a frame. It doesn't mean that a frame is necessarily drawn every 60th of a second — because the thread may be busy doing other things.

Those callbacks will not interrupt other tasks. Again: If other tasks have the main UI thread busy, it's busy, and the framerate suffers.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!