[Proposal] AnimationFrameProvider.requestPostAnimationFrame

AnimationFrameProvider.requestPostAnimationFrame will act as a bookend to AnimationFrameProvider.requestAnimationFrame.

Explainer

This has been a frequently requested feature by web developers that want finer controls for scheduling javascript around rendering deadlines.

1 Like

Question: Does this mean that getBoundingClientRect etc. comes with no cost because the browser has just drawn the dimensions? That would be great!

1 Like

Is this a better or worse place for canvas apps drawing at 60FPS to make their calls compared to the normal requestAnimationFrame?

Yes, that’s correct, although you should keep in mind that you can only get the results after the object has already been displayed. So it may not work for driving layout; you may get a frame of bad pixels.

Potentially yes; a lot depends on the structure of your application. But in general, the advantage of drawing to the canvas from requestPostAnimationFrame is that you get the maximum possible runway for doing that work before the next frame deadline. So, for example, if your canvas code takes 8ms to run, then running it from requestPostAnimationFrame gives you the greatest possibility of not exceeding the rendering deadline.

The was discussed at the WebPerfWG and got positive feedback from all browser vendors. The resolution was to explicitly move this repo to the WICG so that other vendors can provide feedback.

Let’s do this!

I’m curious what the implication of rendering in rPAF has for input event latency. It sounds like rPAF gives more time to meet rendering deadlines, but when are input events delivered? I’ve heard of “rAF aligned input events” which deliver input events just before rAF, but rendering in rPAF means rendering just before input events are delivered. This sounds like it means the app just misses input events, and therefore gets an unnecessary extra frame of input delay.

It would be nice if we could figure out an approach that uses rPAF for most rendering runway and has the lowest possible input latency.

The repo now lives on https://github.com/WICG/requestPostAnimationFrame

Happy incubation!!!

Oh I asked 3 years ago for this and it was declined…

Two thoughts on this:

  • maybe call it requestLayoutFlushed(FF already has a function called promiseLayoutFlushed which gives you the same but also includes a promise) or requestLayoutCalculated/requestLayoutDone/requestAfterLayout or something. some people hate the old name requestAnimationFrame and we shouldn’t built on top of it
  • make sure that as soon as DOM or CSSOM is changed inside of requestPostAnimationFrame a warning is printed in the dev console

Maybe also consider a new method called requestBeforeLayout that is basically the same as requestAnimationFrame but is now run right after requestAnimationFrame and has the same save guard warning. As soon as someone is reading style/layout a console warning should come.

The motivation behind this API is to provide finer control of the scheduling of javascript, so that developers have the power to make the kinds of decisions you’re describing.

Because input events are unpredictable, it probably doesn’t make sense to postpone necessary rendering work on the off chance that an input event will arrive. In the worst case (i.e., input event arrives right after rPAF does some heavy lifting), your code will have done some unnecessary rendering work that gets invalidated before it’s ever displayed. In the best case, you will have smooth rendering and animations because you always get the rendering work done with time to spare.

If you were trying to optimize for both smooth rendering and prompt response to input, you might use an idiom like this:

function runLoop() {
  requestPostAnimationFrame(() => {
    // Get a head start on essential rendering work
    doEarlyRenderingWork();
    requestAnimationFrame(() => {
      // Last-minute adjustments for late-arriving events
      doLateRenderingWork();
      // Re-schedule for every frame.
      runLoop();
    });
  });
}

element.addEventListener('click', evt => {
  if (requiresHeavyLifting(evt)) {
    // Rather than risk missing a rendering deadline and
    // janking, postpone handling the input event until
    // the next frame.
    queueEarlyRenderingWork(evt);
  } else {
    // Respond to the event in the current frame
    queueLateRenderingWork(evt);
  }
});

Ideally, if an input event must be handled ASAP, and the event will cause a visual update, then the page would be structured such that the visual response will be limited to compositor-driven effects that won’t invalidate layout (transform, opacity, z-index). Those kinds of changes can be applied during rAF without missing a rendering deadline. Where that’s not possible, then the developer must pick their poison: respond to the input event a frame late, or risk janking the display.

rAF-aligned input events are the continuous-type events that tend to arrive in streams (e.g. mousemove). In chromium, those events are not delivered as they arrive; instead, they are merged and delivered just prior to running rAF handlers. For these events, it is all the more important to use compositor-driven effects, since there is little runway for doing long-running work prior to rendering.

Hope that helps.

The first priority in designing new web API’s is to respect existing conventions. It’s certainly true that requestAnimationFrame is not universally loved, but since it exists, I’m obliged to stick to it.

The console warning is a good suggestion, and we’re considering doing something along those lines in chromium; but it’s beyond the scope of a standards discussion.