Intro to the Web Animations API

We have plenty of ways to animate things on the web. The answer of which one to use isn’t always easy. Each method comes with pros and cons. Should we use CSS, canvas , Web GL, JavaScript requestAnimationFrame or setInterval ? Maybe SMIL? Oh no — this one is dead now. Maybe some external libraries like jQuery, GreenSock or VelocityJS? These are just a few possible ways to go. If you are keen to know a little bit more about these methods, I encourage you to read a fantastic article “A Comparison of Animation Technologies” by Sarah Drasner on CSS-Tricks.

“Lord of the twins” is the unusual description that Dave Rupert named the Web Animations API in ShopTalk Show episode with Rachel Nabors and Dan Wilson exclusively dedicated to this piece of the W3C spec. It is a combination of hardware accelerated CSS animations and the power of JavaScript. This high-performance API exposes powerful methods that allow us to control the animation of HTML and SVG elements.

Are we ready to use WAAPI? #

The Web Animations API is relatively new — the initial version of the spec was published in June 2012. At the moment of writing this article the browser support isn’t great. Even the browsers that support it offer a very inconsistent level of implementation. If you would like to play around with the bleeding edge parts of this spec then Firefox Nightly build is the best playground. Chrome and Opera are fine. Firefox 48 is coming with WAAPI implementation on board. The status of Safari is under consideration and the road map priority for the IE platform is medium. The first mobile implementation very recently reached Android devices. Safari for iOS — no pressure — but we all are waiting for you.

The good news is that there is a reliable polyfill that provides support for Chrome, Firefox 27+, IE10+ (including Edge), Safari (iOS) 7.1+ and Safari (Mac) 9+. Actually it exists in three versions — web-animations that covers support of basic stable features, web-animations-next that allows us to use newly proposed features and web-animations-next-lite that is a stripped down version of “next” without some of the lesser-used properties.

Basic syntax #

The heading above is the most misleading part of this article because when it comes to the Web Animations API “basic syntax” doesn’t exist. The spec is a behemoth and there are so many constructors associated with the WAAPI (with more coming soon). Let’s just cover the bare minimum that allows us to create something simple.

element . animate ( effect , options );

Please don’t confuse this native animate() function with the jQuery animate() function - they are not related whatsoever. The first parameter effect describes the movement of an animation. At the moment the only natively implemented option that can be used is an array full of keyframes. In the future browsers will allow us to use an object with an array of values (the length of array represents the number of keyframes). You can think about this parameter as an equivalent to @keyframes in CSS.

The absolute minimum that needs to be passed in the options parameter is the duration in milliseconds. Luckily we can pass many more parameters to the AnimationEffectTiming object. Essentially think of this parameter as your CSS animation properties (animation-duration, animation-timing-function, animation-delay etc.).

Don’t believe it until you see it? #

Enough of the theoretical gibberish — time for a practical example. If you have some previous experience with CSS animations, the piece of code below should look very familiar.

document . querySelector ( '.box' ). animate ( [ { offset : 0 , transform : 'none' }, { offset : 0.25 , transform : 'translate(200px, 0)' }, { offset : 0.5 , transform : 'translate(200px, 200px)' }, { offset : 0.75 , transform : 'translate(0, 200px)' }, { offset : 1 , transform : 'none' } ], { delay : 500 , endDelay : 0 , fill : 'both' , iterationStart : 0 , iterations : 50 , duration : 1000 , direction : 'normal' , easing : 'cubic-bezier(.6, 0, 1, .6)' } );

As mentioned before, think about the first parameter as the CSS @keyframes and the second one as CSS animation-* properties inside of a declaration block. On every single keyframe I passed offset although it could be skipped in this case. I did this intentionally to show you how to control the offset of an animation — it does the same job as a percentage value in front of every CSS keyframe. It can be represented as a fraction (ie. 1/4 ) or a decimal number (ie. .25 ). I used endDelay and iterationStart with a value of 0 (this is the default value when the property is not explicitly defined) to give you an overview of all the possible options. To make a clear comparison, have a look at the CSS animation with the properties mirrored.

@ keyframes move { 0 % { transform : none ; } 25 % { transform : translate ( 200 px , 0 ); } 50 % { transform : translate ( 200 px , 200 px ); } 75 % { transform : translate ( 0 , 200 px ); } 100 % { transform : none ; } } . box { animation-name : move ; animation-duration : 1000 ms ; animation-timing-function : cubic-bezier ( .6 , 0 , 1 , .6 ); animation-delay : 500 ms ; animation-iteration-count : 50 ; animation-direction : normal ; animation-fill-mode : both ; animation-play-state : running ; } /* or as a shorthand */ . box { animation : move 500 ms linear 500 ms 10 normal both running ; }

Hopefully this comparison to CSS helps make the syntax clearer. Remember — you are dealing with JS so use camel-case values from the style object, not the property names from CSS. For instance — margin-bottom is marginBottom . It’s just an example, but the animation of margin probably isn’t the best idea from a performance perspective. Paul Lewis and Surma created CSS Triggers - a handy reference of triggered events associated with the animation of particular CSS properties. There is no restriction - whatever you can animate with CSS you can animate via WAAPI (including fancy motion-path animations).

Cool, but does it really generate the same effect? Not really — the behavior of JavaScript’s easing and CSS’ animation-timing-function is different. The WAAPI timing function is applied to the whole iteration of an animation — as expected. As per MDN, the CSS animation-timing-function is applied on each step between keyframes.

For keyframed animations, the timing function applies between keyframes rather than over the entire animation. In other words, the timing function is applied at the start of the keyframe and at the end of the keyframe.

Have a look…

Animation methods and properties #

So far we haven’t seen any clear advantage of using WAAPI over CSS animations. Let’s reveal the difference between reactive JavaScript over declarative CSS. When the animate() function is invoked a new instance of the Animation interface is returned — formerly known as AnimationPlayer . Assigning the animation to a variable allows us to use returned properties, methods and promises. Let’s do it and print to the console these brand new toys.

var move = document . querySelector ( '.box' ). animate ([...], {...}); console . log ( move );

Having access to all this goodness allows us to create more complex effects. If you haven’t dived into the world of ES2015 Promises yet, it’s worth taking a look at “Asynchronous programming (background)" by Dr. Axel Rauschmayer or “ES6 Promises in Depth” by Nicolás Bevacqua. Dan Wilson wrote a helpful article about working with Promises in Web Animations. Time for a simple example…

Let’s talk about some constructors #

Let’s dive deeper. In the previous example we assigned the result of the animate() function to a variable. When animate() is invoked a few things happen — a new KeyframeEffect and Animation object is constructed, the animation starts playing and then is returned. Following the documentation we can manually use the KeyframeEffect and Animation global objects to instantiate a new animation. The only browser that gives us an access to both of them is Firefox Nightly. Thanks again to all amazing polyfill creators! Have a quick look at the syntax.

new Animation ( effect , timeline )

In the current implementation the only valid value passed as the effect parameter is an instance of the KeyframeEffect object. I will show you some more fancy things that we can pass here in a moment.

Another parameter timeline , connects the newly created animation with a source of time for synchronization purposes. As far as I know the only valid value here is the default document timeline accessed by document.timeline . Rachel Nabors (a main contributor to the Web Animations API documentation on MDN) suggests that in the future we may get other new cool options to use.

…in the future there may be timelines associated with gestures or scrolling for example.

Let’s quickly remind ourselves how we did it previously and recreate the same animation in a manually constructed object.

// via function var element = document . querySelector ( '.anime-js' ); var effect = [...]; var options = {...}; var move = element . animate ( effect , options );

// via constructors var element = document . querySelector ( '.anime-js' ); var effect = [...]; var options = {...}; var keyframes = new KeyframeEffect ( element , effect , options ); var move = new Animation ( keyframes , element . ownerDocument . timeline ); move . play ();

At this point you are probably thinking “Yeah, cool, but why should I bother about constructors if I can use the animate() function”. Wait for it!

GroupEffects and SequenceEffects #

As I mentioned before, for the time being the only natively implemented property that can be used to determine the effect of an animation is KeyframeEffect . In the future level 2 spec we will have the opportunity to use more sophisticated constructors like GroupEffect and SequenceEffect . It’s possible to apply a group of animations via CSS but chaining animations together has always been a pain in the arse. More good news — a polyfill allows us to do this today. Examples!

var elem1 = document . querySelector ( '.box1' ); var elem2 = document . querySelector ( '.box2' ); var keyframes = { transform : [ 'none' , 'translate(200px, 0)' , 'translate(200px, 200px)' , 'translate(0, 200px)' , 'none' ] }; var props = { duration : 1000 , easing : 'cubic-bezier(.6, 0, 1, .6)' , iterations : 50 , direction : 'normal' , delay : 500 , fill : 'both' }; var group = new GroupEffect ( [ new KeyframeEffect ( elem1 , keyframes , props ), new KeyframeEffect ( elem2 , keyframes , props ) ] ); var move = new Animation ( group , document . timeline );

And one more for SequenceEffects .

var elem1 = document . querySelector ( '.box1' ); var elem2 = document . querySelector ( '.box2' ); var elem3 = document . querySelector ( '.box3' ); var keyframes = { transform : [ 'none' , 'translate(100px, 0)' , 'translate(100px, 200px)' , 'translate(0, 200px)' , 'none' ] }; var group = new SequenceEffect ( [ new KeyframeEffect ( elem1 , keyframes , 3000 ), new KeyframeEffect ( elem2 , keyframes , 2000 ), new KeyframeEffect ( elem3 , keyframes , 1000 ), new KeyframeEffect ( elem1 , keyframes , 3000 ), new KeyframeEffect ( elem2 , keyframes , 2000 ), new KeyframeEffect ( elem3 , keyframes , 1000 ) ] ); var move = new Animation ( group , document . timeline );

The only thing that confuses me with these two examples is that the animation is playing without invoking a play() method. If anyone can help me to understand this, I owe you a coffee / beer. I promise!

People worth following and useful resources #

To begin with I appreciate that I haven’t demonstrated any particularly crazy or creative examples. The main purpose of this article was to introduce the concept, not to spend time on crafting beautiful demos. Hopefully this overview has helped you out and I’m sure that I’m going to write more about this subject. If you have any questions don’t be afraid to use the comments section below. Thanks for reading!

If you liked this article, please share it on Twitter.

Please enable JavaScript to view the comments powered by Disqus.

Disqus