Optimizing Visual Updates

I want to briefly discuss a technique for optimizing visual updates in our web applications that I think may not get enough attention.

The key is understanding the requestAnimationFrame(..) utility, which is under the general umbrella of HTML5, though it’s been around for quite awhile.

Animation?

The name “requestAnimationFrame” (which I will henceforth refer to as rAF) has some unfortunate semantic implications which may confuse developers of its benefits. You see “animation”, and immediately think, “Oh it’s only for animations, I don’t use those, I write business apps…”. Not true.

In fact, I might go so far as to suggest that nearly all “animations” that you might want to do, even and especially simple “business app” stuff like sliding a notification banner down from the top of the page, popping in a modal window, etc — those are all things you should be doing with CSS transitions, anyway. Rule of thumb: don’t do in JS what you can do in CSS.

Also, let’s address another misconception stemming from the poor name: “request…frame”. At one point, the official MDN page for rAF said something to the effect of: “requests the browser to schedule a repaint”. Ugh. Nope, not true.

In fact, the browser is repainting the page all the time, constantly, up to 60 times per second, even when nothing is changing. It’s the refresh-rate of the page, and the browser does its best to keep that in sync with the refresh-rate of the monitor. That’s the best case scenario, obviously — when the refresh-rates are the same and repaints occur at the same time that the monitor rescans.

rAF doesn’t really control what the browser does with respect to repaints. What it does is let you tell the browser that you’re about to update something visually (which might be a “frame” of “animation”), and it schedules that code (the function callback you pass to rAF) to run right before the next browser repaint.

You often will use rAF in a perpetual/recursive loop where you schedule the next rAF callback from the current one, so that “animation” updates can occur in sync with the browser repaints.

When you use rAF instead of setTimeout(..) for JS-driven animations, the browser knows the updates are visual in nature. This means rAF animation loops are more efficient, because the browser can throttle them down along with its repaint rate when the window is hidden, etc. Also, rAF animations are smoother since there’s no out-of-sync gap between what’s drawn and what’s displayed.

But rAF doesn’t really imply that it has to be an animation. And that’s the problem. Most people just stop once they see “animation” in the name.

rAF What?

So, what is rAF all about, if it’s not just about “animation frames” and it’s not about scheduling browser layout updates?

“requestAnimationFrame” is, frankly, a bad name for the utility. I think it should have been called scheduleVisualUpdate(..), because that’s what it’s really doing: scheduling some of your visual update code to happen right before the next browser repaint.

How can that change how we think about using rAF?

Before we can examine rAF usage, we have to look at some basics first. Let’s imagine a scenario where you have JS that is trying to perform a couple of tasks:

  • Calculate and update the position of several elements on the page
  • Process (sort) the data from an Ajax response

These tasks may or may not be related. For example, you may be putting the sorted data (text) into the elements that you’re shuffling around. Or it may just be that the tasks happen to line up to occur at roughly the same time. Either way, we’ll see that we can in some cases take care to lessen the chance that the first task we run may block/slow-down the other task.

Reducing Contention

You may or may not know that pretty much everything in the UI of your browser, including the page layout and style engine, the JS engine, the garbage collector, etc — all of that is on a single-thread, which means at any given moment, only one of the tasks can be processing.

What this means is, sometimes we have to be careful about what we do, and in what order, as our implicit “scheduling” of tasks may not result in the most optimal scheduling that minimizes contention on the UI thread.

First, let’s think about code like this:

var el = document.getElementById("foo");

el.style.backgroundColor = "blue";

// do some "long running" task, like sorting data

el.style.backgroundColor = "red";

If you’ve never tried something like this before, you may not be aware of the “gotcha” — the backgroundColor updates that you do can be deferred to not have to happen right away, and that means they often will be deferred.

Moreover, because the style/layout engine cannot really run at the same time as the JS engine, the JS engine is almost certainly going to run-to-completion before it effectively lets the background color changing be handled by the style engine and be repainted. This is true even if the browser is ready to repaint, at the natural refresh rate, right in the middle of the “long running” part. So, the setting of the colors will be pushed off until later.

Until when? Likely, until the next time a repaint is going to occur. Setting of blue and then red doesn’t tend to happen separately, as we might assume, but instead gets “batched” up together. They occur essentially simultaneously, which means you will probably never see the blue color, but only see an update to red.

Recalc

The above code had a subtle characteristic to it that you may have missed. We were changing styles which did not require the browser to tell us, at that moment, what the current styles were. As a result, the browser had the option to shift things around in a small way, and it does that kind of optimization all the time, automatically.

What if our code looked like this?

var el = document.getElementById("foo");

var currentWidth = el.innerWidth;
el.style.backgroundColor = "blue";

// do some "long running" task, like sorting data

Now that we are inquiring about the value of el.innerWidth, the engine has to assume we need that data right away, so it actually will force the layout engine to calculate all pending updates so it can answer the innerWidth question accurately.

This could be an expensive task. And notice that it’s doing something that ends up blocking the “long running” task from even starting.

What if the assumption the browser makes (where it doesn’t defer) is a bad assumption, and we don’t actually need the value calculation to block our “long running” task?

We end up with a less-than-optimal scheduling of tasks.

We could have sort of manually batched up our visual update code and had it happen at a “later time” (even just at a later line of code!) such that it wasn’t going to block the other “long running” task.

But let’s go even further with this idea of manual scheduling, and this is where rAF can come into play.

Consider the case where our block of code happens smack in the middle of the ~16.7ms gap between two browser repaints.

d1

Any amount of time our JS engine spends on visual updates might be poor scheduling if it happens right now, because the next earliest time that these updates will become visible to the user is not for several more milliseconds.

By contrast, our “long-running” task is blocked and can’t run for a bit. What if this task has important side effects, such as firing off Ajax requests, spinning up web-workers to take the load, etc. We would certainly want for those things to occur as soon as possible, since they will tend to take advantage of being able to run in parallel outside the contention of the main UI thread.

If we know that our visual recalc/updates are not necessary to run right now, we can not only move them to a lower line of code, but we can actually defer them using rAF, so that they don’t occur until the next repaint is about to happen.

requestAnimationFrame(function(){
    var el = document.getElementById("foo");

    var currentWidth = el.innerWidth;
    el.style.backgroundColor = "blue";

    // ...
});

// do some "long running" task, like sorting data

Even though what we’re doing is not an animation, we’re taking advantage of the fact that rAF effectively defers visual updates until the next repaint, and thereby deferring to a “later time” any of our visual update code, so that right now can be used for non-visual tasks.

d2

This “later time” is right before the next repaint, and it may be 1ms from now, or it may be 15ms from now, but odds are, in general, our visual update logic can be pushed out of the way to make room for other important logic now.

Even if all it did was make room for the garbage collector, this strategy for manual scheduling can tend to make for overall more efficient visual updates.

Next-next-frame

rAF essentially implies “defer this code until the next visual update frame.” Sometimes, we need to defer even beyond that.

For example, remember our case where we wanted to show red then blue? That seems trivial and silly, but there are legitimately cases where we cannot allow our updates to be batched together — we need them to be treated as separate actions.

For example, showing an element by setting its display property to block, and then telling that element to move somewhere else via its CSS transition. If you do both these things in the same “frame”, the CSS engine tends to batch them together, so you don’t see them separately, you see the final result (after the transition). This is annoying!

Sometimes, we need to schedule not just the “next frame”, but the “next-next frame”.

d3

For this purpose, while it’s slightly ugly, a nested rAF call does the job nicely (and is certainly better than the arbitrary setTimeout(..) hacks some have come up with).

requestAnimationFrame(function(){
   el.style.display = "block";
   requestAnimationFrame(function(){
      // fire off a CSS transition on its `top` property
      el.style.top = "300px";
   });
});

Note: Because that code can look ugly and be a little harder to maintain, I created a simple “facade” API for rAF, as part of my h5ive project, which provides queue(..) for rAF(..), and queueAfter(..) for rAF(rAF(..)): animationFrame.

Repainted Message

Think about the “visual update” code you write and whether the updates you’re making need to take up processing time on the UI thread right now, or if they can be deferred via rAF until the next time the repaint will occur, so that the right now is free for other non-visual tasks.

Disclaimer: It’s unlikely this technique will yield massive improvements in UI efficiency and responsiveness, but it should be one more tool in your toolbox that, in general, makes better use of the UI thread, and reduces contention conflicts.

As with all performance optimizations, be careful about spending too much effort optimizing in the non-critical path, or focusing on micro-optimizations and missing the bigger picture. This should be part of your overall strategy, not the main attraction.

Switch to our mobile site