为什么requestAnimationFrame()在帧的末尾而不是在帧的末尾运行我的代码? [英] Why is requestAnimationFrame() running my code at the end of a frame, and not at the start of it?

查看:74
本文介绍了为什么requestAnimationFrame()在帧的末尾而不是在帧的末尾运行我的代码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  var y = 0canvas.height * = 5ctx.fillStyle ='绿色'函数更新(){requestAnimationFrame(更新)ctx.clearRect(0,0,canvas.width,canvas.height)ctx.fillRect(0,y,300,300)y ++}更新() 

对于这个简单的JSBin( https://jsbin.com/yecowob/edit?html,js,output )屏幕上有一个正方形移动的地方,这就是Chrome开发者工具的方式时间轴看起来:

https://imgur.com/Ai0BxY0

据我了解,垂直的灰色虚线是当前帧的末尾和下一帧的开始.在屏幕截图中,我们有一个19.3 ms的帧,其中浏览器几乎什么也不做(很多空闲时间).如果浏览器只是在框架开始时正确运行了所有代码,浏览器就无法避免吗?

但是,如果我将正方形绘制500次,则CPU速度降低了6倍( https://jsbin.com/yecowob/4/edit?js,output),当浏览器完全符合我的要求(开始运行代码作为框架开始),但又再次不同步:

https://imgur.com/Y04XCrz

当它开始在虚线上运行时,fps会平滑得多,但是我只能在浏览器有繁重的工作时才能使它工作.

那为什么为什么requestAnimationFrame()不会每次都在帧的开头立即触发,我该怎么做呢?

非常感谢您可以为此提供的帮助.

解决方案

因为这就是 requestAnimationFrame 的作用:它安排回调在下一个绘画帧"触发,就在实际绘画之前屏幕.

这里的框架"是指事件循环迭代,而不是视觉上的迭代,此后,我将继续使用事件循环迭代"进行区分.

因此,如果我们看一下结构HTML规范所描述的事件循环迭代,我们可以看到" 运行动画框架回调 算法.spec.whatwg.org/multipage/webappapis.html#update-the-rendering"rel =" nofollow noreferrer> 更新渲染 "算法.
该算法在步骤 2 中负责通过检查每个活动文档的"呈现机会"来确定当前的Event Loop迭代是否是绘画.如果不是,那么下面的所有内部步骤都将被丢弃,包括我们的"运行动画帧回调".
这意味着我们的 requestAnimationFrame 计划的回调将仅在非常特殊的事件循环迭代中执行:下一个具有呈现机会的迭代.>

规格没有精确描述此画框"应在哪个频率出现,但是基本上大多数当前的供应商都尝试保持60Hz,而Chrome会将其限制为有源显示器的刷新率.预计Chrome行为会传播到其他供应商.


所以您所描述的是正常的.如果您想要简化的版本,可以将 requestAnimationFrame(fn)视为 setTimeout(fn,time_needed_until_the_next_painting_frame)(这里的微小区别是,超时回调在事件循环迭代的开始,而动画帧回调则在末尾执行.

为什么这样设计?

好吧,因为在大多数情况下,我们希望在屏幕上绘制最新信息.因此,在绘画之前立即触发这些回调可确保应绘画的所有内容均处于最新位置.

但这也意味着,确实,我们不应在其中进行过多的操作,以免失去绘画机会.


现在,我必须注意,有一个正在进行的提案包括一个 requestPostAnimationFrame ,它将安排回调在下一个绘画帧"处触发,即紧随屏幕上的实际绘画之后 .
使用此方法,您将具有预期的行为.
不幸的是,这仍然只是一个建议,尚未包含在规范中,并且不确定是否会存在.

尽管它已经在Chrome中实现,但在"实验性Web平台功能"标志的后面,我们能在正常浏览器中实现其行为的最佳方法是在回调的最开始就安排一个回调下一个事件循环迭代.
这是我为另一个Q/A :

  if(typeof requestPostAnimationFrame!=='function'){monkeyPatchRequestPostAnimationFrame();}requestAnimationFrame(animationFrameCallback);requestPostAnimationFrame(postAnimationFrameCallback);//猴子补丁requestPostAnimationFrame//!\不能从requestAnimationFrame回调内部调用function MonkeyPatchRequestPostAnimationFrame(){console.warn('使用MessageEvent解决方法');const channel = new MessageChannel();const回调= [];让时间戳= 0;让称为=假;channel.port2.onmessage = e =>{叫=假;const toCall = callbacks.slice();callbacks.length = 0;toCall.forEach(fn => {尝试 {fn(时间戳);}抓住(e){}});}window.requestPostAnimationFrame = function(callback){if(typeof callback!=='function'){抛出新的TypeError('Argument 1 is not callable');}callbacks.push(callback);如果(!称为){requestAnimationFrame((time)=> {时间戳=时间;channel.port1.postMessage('');});称为= true;}};}//无效循环,请查看您的开发工具的时间轴,以查看每次触发的位置函数animationFrameCallback(){requestAnimationFrame(animationFrameCallback);}函数postAnimationFrameCallback(){requestPostAnimationFrame(postAnimationFrameCallback)}  

var y = 0
canvas.height *= 5
ctx.fillStyle = 'green'
function update () {
  requestAnimationFrame(update)
   ctx.clearRect(0, 0, canvas.width, canvas.height)
  ctx.fillRect(0, y, 300, 300)
  y++
}
update()

For this simple JSBin (https://jsbin.com/yecowob/edit?html,js,output) where a square moves along the screen, this is how the Chrome dev tools timeline looks:

https://imgur.com/Ai0BxY0

As I understand, the vertical dotted grey line is the end of the current frame and the start of the next one. In the screenshot we have a 19.3 ms frame, where the browser barely does anything (a lot of idle time). Couldn't the browser avoid this if it just ran all the code right as the frame started?

If, however, I draw the square 500 times, on 6x CPU slowdown (https://jsbin.com/yecowob/4/edit?js,output), I get periods when the browser does exactly what I want (to start running the code as the frame starts), but it gets out of sync again:

https://imgur.com/Y04XCrz

When it starts running on the dotted line, the fps is much smoother, but I can only get it to work when the browser has some heavy lifting to do.

So why doesn't requestAnimationFrame() trigger right at the start of the frame every time, and how can I make it do so?

Many thanks for any help you can give me on this.

解决方案

Because that's what requestAnimationFrame does: it schedules a callback to fire at the next "painting frame", just before the actual painting to screen.

Here "frame" refers to an Event Loop iteration, not a visual one, and hereafter I'll keep using "Event Loop iteration" to make the distinction.

So if we take a look at the structure of an Event Loop iteration as described by the HTML specs, we can see that "run the animation frame callbacks" algorithm is called from inside the "update the rendering" algorithm.
This algorithm is responsible at step 2 of determining if the current Event Loop iteration is a painting one or not, by checking the "rendering opportunities" of each active Documents. If it isn't, then all the inner steps below are discarded, including our "run the animation frame callbacks".
This means that our requestAnimationFrame scheduled callbacks will only get executed in a very special Event Loop Iteration: the next one with a rendering opportunity.

Specs don't describe precisely at which frequency this "painting frames" should occur, but basically most current vendors try to maintain 60Hz, while Chrome will make it cap to the active display's refresh rate. It is expected that Chrome behavior spreads to other vendors.


So what you describe is normal. If you want a simplified version of this, you can think of requestAnimationFrame( fn ) as setTimeout( fn, time_needed_until_the_next_painting_frame ) (with the here minor difference that timedout callbacks are executed at the beginning of the Event Loop iteration while animation frame callbacks are executed at the end).

Why has it been designed this way?

Well because most of the time we want to have the freshest information painted on the screen. So having these callbacks to fire right before the painting ensures that everything that should be painted is at its most recent position.

But this also means that indeed, we should not have too heavy operations occurring in there, at the risk of loosing a painting opportunity.


Now, I have to note that there is an ongoing proposal to include a requestPostAnimationFrame, which would schedule callbacks to fire at the next "painting frame", just after the actual painting to screen.
With this method, you'd have the behavior you expected.
Unfortunately, that's still just a proposal, has not been included to the specs, and it's uncertain if it will ever be.

Though it is already implemented in Chrome, behind the "Experimental Web Platform features" flag, the best we can do to approach its behavior in normal browsers is to schedule a callback at the very beginning of the next Event Loop iteration.
Here is an example implementation I made for an other Q/A:

if (typeof requestPostAnimationFrame !== 'function') {
  monkeyPatchRequestPostAnimationFrame();
}

requestAnimationFrame( animationFrameCallback );
requestPostAnimationFrame( postAnimationFrameCallback );

// monkey-patches requestPostAnimationFrame
//!\ Can not be called from inside a requestAnimationFrame callback
function monkeyPatchRequestPostAnimationFrame() {
  console.warn('using a MessageEvent workaround');
  const channel = new MessageChannel();
  const callbacks = [];
  let timestamp = 0;
  let called = false;
  channel.port2.onmessage = e => {
    called = false;
    const toCall = callbacks.slice();
    callbacks.length = 0;
    toCall.forEach(fn => {
      try {
        fn(timestamp);
      } catch (e) {}
    });
  }
  window.requestPostAnimationFrame = function(callback) {
    if (typeof callback !== 'function') {
      throw new TypeError('Argument 1 is not callable');
    }
    callbacks.push(callback);
    if (!called) {
      requestAnimationFrame((time) => {
        timestamp = time;
        channel.port1.postMessage('');
      });
      called = true;
    }
  };
}


// void loops, look at your dev-tools' timeline to see where each fires
function animationFrameCallback() {
  requestAnimationFrame( animationFrameCallback );
}
function postAnimationFrameCallback() {
  requestPostAnimationFrame( postAnimationFrameCallback )
}

这篇关于为什么requestAnimationFrame()在帧的末尾而不是在帧的末尾运行我的代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆