Animating

Animating like you just don’t care with Element.animate

In Firefox 48 we’re shipping the Element.animate() API — a new way to programmatically animate DOM elements using JavaScript. Let’s pause for a second — “big deal”, you might say, or “what’s all the fuss about?” After all, there are already plenty of animation libraries to choose from. In this post I want to explain what makes Element.animate() special.

What a performance

Element.animate() is the first part of the Web Animations API that we’re shipping and, while there are plenty of nice features in the API as a whole, such as better synchronization of animations, combining and morphing animations, extending CSS animations, etc., the biggest benefit of Element.animate() is performance. In some cases, Element.animate() lets you create jank-free animations that are simply impossible to achieve with JavaScript alone.

Don’t believe me? Have a look at the following demo, which compares best-in-class JavaScript animation on the left, with Element.animate() on the right, whilst periodically running some time-consuming JavaScript to simulate the performance when the browser is busy.

Performance of regular JavaScript animation vs Element.animate()To see for yourself, try loading the demo in the latest release of Firefox or Chrome. Then, you can check out the full collection of demos we’ve been building!

When it comes to animation performance, there is a lot of conflicting information being passed around. For example, you might have heard amazing (and untrue) claims like, “CSS animations run on the GPU”, and nodded along thinking, “Hmm, not sure what that means but it sounds fast.” So, to understand what makes Element.animate() fast and how to make the most of it, let’s look into what makes animations slow to begin with.

Animations are like onions (Or cakes. Or parfait.)

In order for an animation to appear smooth, we want all the updates needed for each frame of an animation to happen within about 16 milliseconds. That’s because browsers try to update the screen at the same rate as the refresh rate of the display they’re drawing to, which is usually 60Hz.

On each frame, there are typically two things a browser does that take time: calculating the layout of elements on the page, and drawing those elements. By now, hopefully you’ve heard the advice, “Don’t animate properties that update layout.” I am hopeful here — current usage metrics suggest that web developers are wisely choosing to animate properties like transform and opacity that don’t affect layout whenever they can. (color is another example of a property that doesn’t require recalculating layout, but we’ll see in a moment why opacity is better still.)

If we can avoid performing layout calculations on each animation frame, that just leaves drawing the elements. It turns out that programming is not the only job where laziness is a virtue — indeed animators worked out a long time ago that they could avoid drawing a bunch of very similar frames by creating partially transparent cels, moving the cels around on top of the background, and snapshotting the result along the way.

Example of cels used to create animation frames

Example of creating animation frames using cels.
(Of course, not everyone uses fancy cels; some people just cut out Christmas cards.)

A few years ago browsers caught on to this “pull cel” trick. Nowadays, if a browser sees that an element is moving around without affecting layout, it will draw two separate layers: the background and the moving element. On each animation frame, it then just needs to re-position these layers and snapshot the result without having to redraw anything. That snapshotting (more technically referred to as compositing) turns out to be something that GPUs are very good at. What’s more, when they composite, GPUs can apply 3D transforms and opacity fades all without requiring the browser to redraw anything. As a result, if you’re animating the transform or opacity of an element, the browser can leave most of the work to the GPU and stands a much better chance of making its 16ms deadline.

Hint: If you’re familiar with tools like Firefox’s Paint Flashing Tool or Chrome’s Paint Rectangles you’ll notice when layers are being used because you’ll see that even though the element is animating nothing is being painted! To see the actual layers, you can set layers.draw-borders to true in Firefox’s about:config page, or choose “Show layer borders” in Chrome’s rendering tab.

You get a layer, and you get a layer, everyone gets a layer!

The message is clear — layers are great and you are expecting that surely the browser is going to take full advantage of this amazing invention and arrange your page’s contents like a mille crêpe cake. Unfortunately, layers aren’t free. For a start, they take up a lot more memory since the browser has to remember (and draw) all the parts of the page that would otherwise be overlapped by other elements. Furthermore, if there are too many layers, the browser will spend more time drawing, arranging, and snapshotting them all, and eventually your animation will actually get slower! As a result, a browser only creates layers when it’s pretty sure they’re needed — e.g. when an element’s transform or opacity property is being animated.

Sometimes, however, browsers don’t know a layer is needed until it’s too late. For example, if you animate an element’s transform property, up until the moment when you apply the animation, the browser has no premonition that it should create a layer. When you suddenly apply the animation, the browser has a mild panic as it now needs to turn one layer into two, redrawing them both. This takes time, which ultimately interrupts the start of the animation. The polite thing to do (and the best way to ensure your animations start smoothly and on time) is to give the browser some advance notice by setting the will-change property on the element you plan to animate.

For example, suppose you have a button that toggles a drop-down menu when clicked, as shown below.

Example of using will-change to prepare a drop-down menu for animation

Live example

We could hint to the browser that it should prepare a layer for the menu as follows:

nav {
  transition: transform 0.1s;
  transform-origin: 0% 0%;
  will-change: transform;
}
nav[aria-hidden=true] {
  transform: scaleY(0);
}

But you shouldn’t get too carried away. Like the boy who cried wolf, if you decide to will-change all the things, after a while the browser will start to ignore you. You’re better off to only apply will-change to bigger elements that take longer to redraw, and only as needed. The Web Console is your friend here, telling you when you’ve blown your will-change budget, as shown below.

Screenshot of the DevTools console showing a will-change over-budget warning.

Animating like you just don’t care

Now that you know all about layers, we can finally get to the part where Element.animate() shines. Putting the pieces together:

  • By animating the right properties, we can avoid redoing layout on each frame.
  • If we animate the opacity or transform properties, through the magic of layers we can often avoid redrawing them too.
  • We can use will-change to let the browser know to get the layers ready in advance.

But there’s a problem. It doesn’t matter how fast we prepare each animation frame if the part of the browser that’s in control is busy tending to other jobs like responding to events or running complicated scripts. We could finish up our animation frame in 5 milliseconds but it won’t matter if the browser then spends 50 milliseconds doing garbage collection. Instead of seeing silky smooth performance our animations will stutter along, destroying the illusion of motion and causing users’ blood pressure to rise.

However, if we have an animation that we know doesn’t change layout and perhaps doesn’t even need redrawing, it should be possible to let someone else take care of adjusting those layers on each frame. As it turns out, browsers already have a process designed precisely for that job — a separate thread or process known as the compositor that specializes in arranging and combining layers. All we need is a way to tell the compositor the whole story of the animation and let it get to work, leaving the main thread — that is, the part of the browser that’s doing everything else to run your app — to forget about animations and get on with life.

This can be achieved by using none other than the long-awaited Element.animate() API! Something like the following code is all you need to create a smooth animation that can run on the compositor:

elem.animate({ transform: [ 'rotate(0deg)', 'rotate(360deg)' ] },
             { duration: 1000, iterations: Infinity });

Screenshot of the animation produced: a rotating foxkeh
Live example

By being upfront about what you’re trying to do, the main thread will thank you by dealing with all your other scripts and event handlers in short order.

Of course, you can get the same effect by using CSS Animations and CSS Transitions — in fact, in browsers that support Web Animations, the same engine is also used to drive CSS Animations and Transitions — but for some applications, script is a better fit.

Am I doing it right?

You’ve probably noticed that there are a few conditions you need to satisfy to achieve jank-free animations: you need to animate transform or opacity (at least for now), you need a layer, and you need to declare your animation up front. So how do you know if you’re doing it right?

The animation inspector in Firefox’s DevTools will give you a handy little lightning bolt indicator for animations running on the compositor. Furthermore, as of Firefox 49, the animation inspector can often tell you why your animation didn’t make the cut.

Screenshot showing DevTools Animation inspector reporting why the transform property could not be animated on the compositor.

See the relevant MDN article for more details about how this tool works.

(Note that the result is not always correct — there’s a known bug where animations with a delay sometimes tell you that they’re not running on the compositor when, in fact, they are. If you suspect DevTools is lying to you, you can always include some long-running JavaScript in the page like in the first example in this post. If the animation continues on its merry way you know you’re doing it right — and, as a bonus, this technique will work in any browser.)

Even if your animation doesn’t qualify for running on the compositor, there are still performance advantages to using Element.animate(). For instance, you can avoid reparsing CSS properties on each frame, and allow the browser to apply other little tricks like ignoring animations that are currently offscreen, thereby prolonging battery life. Furthermore, you’ll be on board for whatever other performance tricks browsers concoct in the future (and there are many more of those coming)!

Conclusion

With the release of Firefox 48, Element.animate() is implemented in release versions of both Firefox and Chrome. Furthermore, there’s a polyfill (you’ll want the web-animations.min.js version) that will fall back to using requestAnimationFrame for browsers that don’t yet support Element.animate(). In fact, if you’re using a framework like Polymer, you might already be using it!

There’s a lot more to look forward to from the Web Animations API, but we hope you enjoy this first installment (demos and all)!

View full post on Mozilla Hacks – the Web developer blog

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)

Animating with javascript: from setInterval to requestAnimationFrame

Animating DOM elements[1] or the content of a canvas is a classical use case for setInterval. But the interval is not as reliable as it seems, and a more suitable API is now available…

Animating with setInterval

To animate an element moving 400 pixels on the right with javascript, the basic thing to do is to move it 10 pixels at a time on a regular interval.

An HTML5 game based on this logic would normally run at ~60fps[2], but if the animations were too complex or running on a low-spec. device (a mobile phone for instance) and processing a frame were taking more than 16ms, then the game would run at a lower framerate: when processing 1 frame takes 33ms, the game runs at 30fps and game elements move twice as slowly as they should. Animations would still look smooth enough, but the game experience would be altered.

Animating at constant speed

To animate at constant speed, we need to calculate the time delta since the last frame and move the element proportionally.

Animating with requestAnimationFrame

Since the interval parameter is irrelevant in complex animations, as there’s no guarantee that it will be honored, a new API has been designed: requestAnimationFrame. It’s simply a way to tell the browser “before drawing the next frame on the screen, execute this game logic/animation processing”. The browser is in charge of choosing the best moment to execute the code, which results in a more efficient use of resources[3].

Here’s how an animation with requestAnimationFrame would be written.
Note: Following code snippets don’t include feature detections and workarounds necessary to work in current browsers. Should you want to play with them, you should try the ready-to-use animLoop.js.

Dealing with inactive tabs

requestAnimationFrame was built with another benefit in mind: letting the browser choose the best frame interval allows to have a long interval in inactive tabs. Users could play a CPU intensive game, then open a new tab or minimize the window, and the game would pause[4], leaving resources available for other tasks.
Note: the potential impact of such behavior on resource and battery usage is so positive that browser vendors decided to adopt it for setTimeout and setInterval as well[5].

This behavior also means that the calculated time delta might be really high when switching back to a tab containing an animation. This will result in animation appearing to jump or creating “wormholes[6], as illustrated here:

Wormholes can be fixed by clamping the time delta to a maximum value, or not rendering a frame when the time delta is too high.

Problems with animation queues

Libraries such as jQuery queue animations on elements to execute them one after the other. This queue is generally only used for animations that are purposefully consecutive.
But if animations are triggered by a timer, the queue might grow without bound in inactive tabs, as paused animations stack up in the queue. When switching back to affected tabs, a user will see a large number of animations playing consecutively when only one should happen on a regular interval.

This problem is visible in some auto-playing slideshows such as mb.gallery. To work around it, developers can empty animation queues before triggering new animations[7].

Conclusion

The delays of setTimeout and setInterval and of course requestAnimationFrame are unpredictable and much longer in inactive tabs. These facts should be taken into account not only when writing animation logic, but in fps counters, time countdowns, and everywhere time measurement is crucial.

[1] The DOM can now be animated with CSS3 Transitions and CSS3 Animations.
[2] 1 frame every 16ms is 62.5 frames per second.
[3] See the illustration of this fact on msdn.
[4] The behavior of requestAnimationFrame in inactive tabs is still being worked on at the w3c, and might differ in other browsers.
[5] See related Firefox bug and related chromium bug.
[6] This term was first coined by Seth Ladd in his “Intro to HTML5 Game Development” talk.
[7] See documentation of your js library, such as effects and stop() for jQuery.

View full post on Mozilla Hacks – the Web developer blog

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.22_1171]
Rating: 0 (from 0 votes)