Pic­ture the scene: you like your site, but it’s a bit bland. You want it to POP. You start by adding a few sim­ple CSS ani­ma­tions to your but­tons. They look pret­ty good. Then you decide you want to have nice page tran­si­tions. No prob­lem, there are lots of libraries to help with that — you pop one in. Maybe you should ani­mate your land­ing page? In fact, why not have each ele­ment ani­mate at dif­fer­ent times. Hmmm, might need some JavaScript for that. OK, well that works but let’s try it out on my phone …

Soon it’s 3a.m. and you’re knee-deep in the Chrome Dev-Tools try­ing to make sense of your pathet­ic 3fps page-load expe­ri­ence. You haven’t seen your friends or fam­i­ly in days.

The point is, once you get beyond sim­ple fade-ins, ani­ma­tions on the web are hard.

The goal of this post is to try give you the con­text as to why they’re hard, as well as a brief his­to­ry and run-down of the avail­able approach­es to tack­ling animation.

This post won’t give you a detailed expla­na­tion of how each approach works, but it will give you the under­stand­ing you need to be able to han­dle ani­ma­tion on the web.


To get a good overview of where we cur­rent­ly stand with brows­er ani­ma­tions, here’s what we’re going to look at:

  • Flash 😱
  • SMIL (smile”)
  • CSS Tran­si­tions and Animations
  • JavaScript ani­ma­tions
  • Web Ani­ma­tion API (WAAPI)
  • 3rd par­ty ani­ma­tion frame­works and libraries

For con­text, let’s begin with the most infa­mous (and now defunct) approach.


Fer­al’s first web­site cir­ca 1994

Back when browsers were still at war, there weren’t many cross-plat­form stan­dards to point to. The web was young and exper­i­men­ta­tion was rife. Devel­op­ers want­ed to be able to cre­ate inter­ac­tive web­sites and games, what­ev­er the cost.

Flash was the answer. It was a pro­pri­etary, closed-source appli­ca­tion (even­tu­al­ly owned by Adobe) that allowed devel­op­ers to build these inter­faces and games and then dump them into the brows­er via the <object> tag and a brows­er plug-in. The brows­er could inter­act with them but it was the respon­si­bil­i­ty of the installed Flash plug-in to run every­thing. Essen­tial­ly the brows­er was out­sourc­ing its job to Flash.

While Flash was a great tech­nol­o­gy, it went against the core prin­ci­ple of the web; to be open and acces­si­ble. As the web matured, a slew of new stan­dards and specs began to take shape intro­duc­ing the abil­i­ty to do many of the things Flash could do, but native­ly via the brows­er. At the same time, smart­phones came along. Apple famous­ly did­n’t want to sup­port Flash in their iPhone and the writ­ing was on the wall.

To cut a long sto­ry short, Flash sup­port has been dep­re­cat­ed on all major browsers and Adobe will be putting it out to pas­ture in 2020RIP.

So how do we ani­mate on the web with­out Flash?


Flash pro­vid­ed the ecosys­tem for cre­at­ing ani­ma­tions that could be plugged into the web, but the core com­po­nent at the heart of these ani­ma­tions was vec­tor graph­ics.

Vec­tor graph­ics are res­o­lu­tion inde­pen­dent image files. In con­trast to raster image for­mats such as pngsjpgs and gifs, vec­tor graph­ics are made up of paths and shapes instead of pix­els. These res­o­lu­tion-inde­pen­dent ele­ments are per­fect for icons and illus­tra­tions, par­tic­u­lar­ly on todays high-res­o­lu­tion screens.

While Flash was devour­ing the web in the 2000s, the SVG stan­dard was being built to sup­port open vec­tor graph­ics in the brows­er. Although these weren’t well sup­port­ed until the 2010s, they pro­vid­ed the bedrock for high qual­i­ty graph­ics and ani­ma­tions via the .svg file for­mat and <svg>HTML element.

You are prob­a­bly famil­iar with SVGs. They are based on XML, so they look very like our reg­u­lar HTML:

      <rect ...>
      <path .. >
      <line ...>
      <circle ...>

Around the same time SVGs were being stan­dard­ised to han­dle vec­tor graph­ics, we were also look­ing for ways to stan­dard­ise mul­ti­me­dia on the web in gen­er­al. Pro­pri­ety plug-ins were han­dling almost all of the audio (remem­ber RealPlay­er?), video and ani­ma­tion on the web and this was becom­ing a closed-source quagmire.

To han­dle this, the SMIL (“Syn­chro­nised Mul­ti­me­dia Inte­gra­tion Lan­guage” 🤷🏻‍♂️) stan­dard was devel­oped. Pro­nounced smile, this was an ambi­tious stan­dard aimed at pro­vid­ing the frame­work for deliv­er­ing video, audio and ani­ma­tions in a more open, user-friend­ly and acces­si­ble way. If HTML allowed you to deliv­er a doc­u­ment to the brows­er, SMIL would allow you to deliv­er and coor­di­nate mul­ti­me­dia to the browser.

Includ­ed in the SMIL stan­dard were the tools need­ed to ani­mate SVGs. To do so it aug­ment­ed SVGs with ani­ma­tion-spe­cif­ic ele­ments to be used along­side the reg­u­lar shapes and paths:

  • <animate>
  • <animateMotion>
  • <animateTransform>

These allowed you to do com­plex and coor­di­nat­ed ani­ma­tions with­in a sin­gle SVG file.

In sum­ma­ry, SVGs and SMIL give you the toolset to cre­ate Flash-like ani­ma­tions in the brows­er using an open stan­dard. But what are its short­com­ings with regard to animations?

So while it’s still around in 2020, it’s prob­a­bly best to look beyond SMIL for ani­mat­ing on the web.

The good news is that you can still ani­mate SVGs with­out SMIL using the oth­er APIs and tech­nolo­gies that we’re going to dis­cuss below.

CSS Tran­si­tions, Ani­ma­tions & Transforms

As the web found its feet in the mid-2000s, one of the big leaps for­ward was the intro­duc­tion of both HTML5 and CSS3 (~2005). These remain the lat­est releas­es of the HTML and CSS stan­dards and they intro­duced a slew of new fea­tures that helped unite web tech­nolo­gies across browsers.

One of the most antic­i­pat­ed fea­tures was the joint-intro­duc­tion of CSS ani­ma­tions, tran­si­tions and trans­forms via the fol­low­ing properties:

  • transform: ...
  • transition: ...
  • animate: ... + @key-frames

These three addi­tions paved the way for native ani­ma­tions in the brows­er and are the go-to for most devel­op­ers these days that want to get start­ed with brows­er animations.

Because they were intro­duced at the same time, they are often assumed to all be required when ani­mat­ing but this is not the case. It’s vital to under­stand these as 3 dis­tinct (albeit relat­ed) features.

Let’s have a look at them in a bit more detail.

CSS Trans­forms

The transform prop­er­ty allows you change the appear­ance of an ele­ment. For exam­ple you can:

  • Scale
  • Skew
  • Trans­late
  • Rotate

Just like you would an image in Photoshop.

Here’s the impor­tant part: on its own, a transform has absolute­ly noth­ing to do with ani­ma­tion. Trans­forms can be ani­mat­ed but they have noth­ing to do with ani­ma­tion themselves.

CSS Tran­si­tions

The transition prop­er­ty is the baby-sib­ling in the CSS ani­ma­tion fam­i­ly, dis­tinct from its old­er sib­lings animate and @key-frames.

Let’s drop the metaphor and be clear: tran­si­tions and ani­ma­tions are sep­a­rate fea­tures to be used in dif­fer­ent circumstances.

Tran­si­tions allow you to define an ani­ma­tion that runs when a par­tic­u­lar prop­er­ty on an ele­ment changes, for example:

.truck {
	transition: transform 1s ease-in;

.truck.drive {
	transform: translate3d(100%, 0, 0);

Here, when we add the .drive class to our truck the brows­er applies a transform that moves the truck 100% to the right. 

  1. How is this tran­si­tion trig­gered? For the tran­si­tion to begin, our ele­ment needs to have the .drive class applied. We could use pseu­do-selec­tor like :hover in our tran­si­tions, but usu­al­ly these trig­gers are applied via JavaScript. Either way, tran­si­tions require a trig­ger to run.
  2. What if we want this to run more than once (i.e. loop)? We can’t do this with tran­si­tions. Tran­si­tions only run once, in response to their trigger.
  3. What if we want inter­me­dia stages” to our opac­i­ty tran­si­tion? For exam­ple, maybe fade-in to .2 opac­i­ty, hold there, then fin­ish fad­ing-in to 1.0 opac­i­ty. We can’t con­trol that with tran­si­tions. Tran­si­tions only have two states”.

The impli­ca­tion of this is that tran­si­tions are use­ful when you want to ani­mate some­thing that has a def­i­nite start and end-state and only needs to run once when triggered.

CSS Ani­ma­tions

The animate prop­er­ty and @keyframes at-rule address some of the ques­tions left unan­swered by tran­si­tions, but remem­ber that they are a dis­tinct fea­ture meant for dif­fer­ent things.

While tran­si­tions are good for one-off ani­ma­tions these are used for cre­at­ing mul­ti-stage ani­ma­tions that run more than once.

What does an ani­ma­tion look like?

.wheels {
	animation-name: spin;
	animation-duration: .3s;
	animation-iteration-count: infinite;
	animation-timing-function: linear;
	animation-direction: normal;

@keyframes spin {
	from {
		transform: rotate(0deg);
	to {
		transform: rotate(360deg);
  1. In con­trast to a tran­si­tion, this ani­ma­tion begins imme­di­ate­ly when the page loads. We could add the animation prop­er­ty to a trig­gered class or pseu­do-selec­tor, but we don’t have to. In oth­er words, ani­ma­tions can run with­out a trigger.
  2. The next thing to note is that our ani­ma­tion here can have >2 states. We can add as many states as we want in our @keyframes rule to give us real­ly fine-grained con­trol over how the ani­ma­tion pro­gress­es. Ani­ma­tions can have mul­ti­ple states
  3. We can also con­trol how many times this ani­ma­tion is run. In our exam­ple it will spin indef­i­nite­ly as we’ve indi­cat­ed infinite as the inter­ac­tion count. Ani­ma­tions can run repeatedly
  4. Final­ly it’s worth under­stand­ing that we could write an ani­ma­tion that does exact­ly what our tran­si­tion does. It would be a bit more labo­ri­ous but you can write all tran­si­tions as ani­ma­tions (but not visa-ver­sa).

The bot­tom line here is that CSS tran­si­tions and ani­ma­tions are com­pli­men­ta­ry fea­tures to be used in dif­fer­ent scenarios.

CSS tran­si­tions allow you to write quick ani­ma­tions that run in response to a trig­ger while CSS ani­ma­tions give you more pow­er­ful con­trol over the pro­gres­sion of an ani­ma­tion as well as how it executes.

Now that we’ve looked at what CSS tran­si­tions and ani­ma­tions can do, let’s look at what they can’t do.

Time­lines, Con­trol and Coordination

OK, so what if we want to do more than just one-off or repeat­ing ani­ma­tions? How can we con­trol and coor­di­nate with CSS ani­ma­tions? Spoil­er: this is where the wheels come off.

How do we sequence mul­ti­ple ani­ma­tions along some sort of time­line? There arenative events for ani­ma­tions and tran­si­tions, like animationend and transitionend but they are lim­it­ed and we have to write our own JavaScript to time everything.

How do we dynam­i­cal­ly play or pause ani­ma­tions? We canuse the animation-play-stateCSS prop­er­ty to wres­tle some con­trol over state, but again we have to write the skele­ton code.

How do we get infor­ma­tion about an ani­ma­tion’s cur­rent state? We can use the animation-play-state as men­tioned above, but what about find­ing out exact­ly what tim­ing the ani­ma­tion is cur­rent­ly at? No can do.

How do we change our ani­ma­tions in response to user input? With great hard­ship and hackery.

How do we seek to a par­tic­u­lar part of an an animation?

You get the point — when it comes to cre­at­ing more com­plex ani­ma­tions there are many lim­i­ta­tions with the CSS approach that leave us short of a well-round­ed solution.

JavaScript Ani­ma­tions

To deal with these lim­i­ta­tions, let’s take a rad­i­cal step and aban­don CSS ani­ma­tions and tran­si­tions alto­geth­er for the time being. We’ll go back in time to the ear­ly days of the web and look at how we build ani­ma­tions man­u­al­ly in JavaScript.

So where do we start? To be able to ani­mate with JavaScript we need tim­ing func­tions … lot’s of tim­ing functions.

Using setTimeout and setInterval

JavaScript offers two func­tions that help us cre­ate animations: 

  • window.setTimeout
  • window.setInterval

These let us set indi­vid­ual timers and repeat­ing timers. We can use these func­tions as the build­ing blocks of our animations.

Here is the same fade-in ani­ma­tion using CSS and JavaScript:

.square {
  opacity: 0;

.square.fade-in {
  opacity: 1;
  transition: opacity 30ms linear;
var play = function() {
	var el = document.getElementById("square");
	var opacity = 0;
	var timer = setInterval(fadeIn, 30);
	var fadeIn = function() {
		if (opacity > 1) {
		} else {
			opacity += .1;
			el.style.opacity = opacity;


Although these do the same thing, they look very dif­fer­ent. To begin with, it’s much hard­er to read the JavaScript ani­ma­tion at a glance and there’s much more code involved. Why?

Well with JavaScript we are telling the brows­er exact­ly how to per­form the ani­ma­tion — we’ve writ­ten the imple­men­ta­tion our­selves. With the CSS tran­si­tion, we out­lined what our ani­ma­tion should do but we did­n’t say how to actu­al­ly do it — we left that to the brows­er to actu­al­ly imple­ment behind-the-scenes.

This is an impor­tant dis­tinc­tion that begins to illus­trate a fun­da­men­tal dif­fer­ence between CSS and JavaScript ani­ma­tions that we’ll come back to in a bit.


So far, we’re not doing any bet­ter using JavaScript than native CSS tran­si­tions and ani­ma­tions. We want a time­line, we want syn­chro­ni­sa­tion and coor­di­na­tion, WEWANTCONTROL.

By default, JavaScript does­n’t give us any of the abstrac­tions we need to con­trol our ani­ma­tions but that’s not to say we can’t cre­ate them ourselves.

We could use JavaScript to start devel­op­ing our own API:

class Animation() {
	constructor() {}	
	play() {}
	pause() {}
	reverse() {}

class Timeline() {
	add() {}
	remove() {}

OK, this is look­ing good, but it’s going to be a huge amount of work to actu­al­ly devel­op this out.

Thank­ful­ly there are many peo­ple who have blazed a trail before us. Many mature 3rd par­ty libraries already exist that pro­vide entire ani­ma­tion ecosys­tems that we can use on our project.

We’ll touch on some of these options lat­er, but the point here is that JavaScript isn’t great at allow­ing us to define ani­ma­tions (they become hard-to-read and tedious to write) but it cer­tain­ly gives us the tools to con­trol them once they are up and running.

The Big Issue: CSS vs JavaScript

Hope­ful­ly you’re start­ing to see the fric­tion aris­ing here between CSS and JavaScript.

CSS is a great place to define all of our ani­ma­tions but it’s not the place to con­trol ani­ma­tions. JavaScript on the oth­er hand isn’t a great place to define our ani­ma­tions but it is a great place to con­trol them.

Why is this fric­tion aris­ing? Essen­tial­ly it’s because:

  • CSS is declar­a­tive, JavaScript is imper­a­tive
  • CSS is con­cerned with pre­sen­ta­tion, JavaScript is con­cerned behav­iour.

What does this mean? In JavaScript we focus on telling the brows­er how to do things: if the user clicks this, do that”. This is imper­a­tive: we give the brows­er the set of steps and con­trol log­ic that it needs to car­ry out our tasks. In CSS how­ev­er we focus on telling the brows­er what to do: Make this <div> blue (but I don’t care how you do it)”. This is declar­a­tive.

A full ani­ma­tion solu­tion needs to be both declar­a­tive AND imper­a­tive. It should be con­cerned with pre­sen­ta­tion AND behaviour.

This is the fun­da­men­tal rea­son ani­ma­tions in the brows­er seem so com­plex. Ani­ma­tion sits right in the mid­dle of where CSS and JavaScript are designed to over­lap. For the per­fect solu­tion you need to be able to eas­i­ly define your ani­ma­tions but you also need to able to intu­itive­ly con­trol them. This should hap­pen through the same API, not using a cock­tail of CSS and JavaScript.

Thank­ful­ly there is a newishAPI that does exact­ly this: the Web Ani­ma­tion API or WAAPI”.

Web Ani­ma­tions API (WAAPI)

WAAPI is a rel­a­tive­ly new brows­er stan­dard cre­at­ed to address the fric­tion we’ve encoun­tered with each of the ani­ma­tion approach­es we’ve dis­cussed so far. 

The inten­tion of this stan­dard is to encap­su­late all the above approach­es and give us a com­mon lan­guage to describe and con­trol ani­ma­tions in the browser:

CSS Tran­si­tions, CSS Ani­ma­tions, and SVG all pro­vide mech­a­nisms that gen­er­ate ani­mat­ed con­tent on a Web page. Although the three spec­i­fi­ca­tions pro­vide many sim­i­lar fea­tures, they are described in dif­fer­ent terms. This spec­i­fi­ca­tion pro­pos­es an abstract ani­ma­tion mod­el that encom­pass­es the com­mon fea­tures of all three spec­i­fi­ca­tions. This mod­el is back­wards-com­pat­i­ble with the cur­rent behav­ior of these spec­i­fi­ca­tions such that they can be defined in terms of this mod­el with­out any observ­able change.”

It gives us the abil­i­ty to declar­a­tive­ly define our ani­ma­tions in a CSS-like fash­ion, while pro­vid­ing the abstrac­tions, func­tions and meth­ods we need to prop­er­ly con­trol our ani­ma­tions in JavaScript.

At a high lev­el, the WAAPI dis­tils every­thing we’ve dis­cussed so far into three concepts:

  • Timeline the abil­i­ty to sequence animations
  • Animation the abil­i­ty to con­trol animations
  • Effects the actu­al ani­ma­tions themselves

These con­cepts are back­wards com­pat­i­ble, so they should con­cep­tu­al­ly map onto CSS tran­si­tions and ani­ma­tions as well as SMIL.

In prac­tise, the API intro­duces the fol­low­ing objects and meth­ods to the brows­er ecosystem:

  • A document.timeline object
  • An animate method on DOM elements
  • Animation objects (returned from the above animate() method) that have con­trol meth­ods such as play() and pause()
  • Effect objects that allow you define your request­ed animations

What does this mean for us, the devel­op­er, try­ing to get our head around web animations?

  • We no longer have to work in the declar­a­tive world of CSS — we have moved our ani­ma­tions out of the pre­sen­ta­tion lay­er, into the behav­iour layer.
  • We gain the per­for­mance ben­e­fits of work­ing direct­ly with the browser’s ani­ma­tion engine
  • We can stop/​start animations
  • We can check ani­ma­tion state
  • We now have a timeline
  • We can group and sequence animations

So this new stan­dard gives a ful­ly fledged, native ani­ma­tion frame­work to work with, with­out hav­ing to go to a 3rd par­ty library for emo­tion­al support.

What’s the bad news? The bad news is that this is a rel­a­tive­ly new stan­dard and there­fore does­n’t have full sup­port. Fur­ther­more, only some of the API has been built-out so far, so some of the more advanced fea­tures are still a lit­tle while off (ani­ma­tion motion along a path for example).

But it’s a great start for uni­fy­ing all of the ani­ma­tion con­cepts into a native brows­er stan­dard that we can all understand.

Ani­ma­tion Frameworks

With the Web Ani­ma­tions API still in the works, for com­plex ani­ma­tion we may want to fall back to tried-and-test­ed 3rd par­ty libraries.

For this there are many options. Some are niche and cov­er par­tic­u­lar require­ments while oth­ers are broad, giv­ing you the frame­work and tools to do what you want.

Here are a cou­ple of pop­u­lar examples:

  • GreenSock — The de-fac­to ani­ma­tion library for com­plex ani­ma­tion. Gives you every­thing and the kitchen sink.
  • Animé.js — Anoth­er ful­ly fea­tured engine” that pro­vides most of what you need for com­plex animation.
  • Velocity.js — Light­weight library for more basic jQuery-like animations.
  • Pop­mo­tion — Anoth­er fea­ture rich full ani­ma­tion environment.
  • Scroll­Re­veal — An exam­ple of a niche ani­ma­tion library for ani­mat­ing on win­dow scroll

The impor­tant thing to note is that all these libraries use one or more of the approach­es we’ve men­tioned: ful­ly cus­tom JavaScript, CSS transitions/​animations, WAAPI or a mix of all three.

The obvi­ous down­side of using these libraries is that they intro­duce a depen­den­cy into your project and a con­cep­tu­al over­head. On the upside, they add a com­mon abstrac­tion on top of all the approach­es we’ve men­tioned today, mak­ing it eas­i­er to get started.


Whew. In this post we’ve gone through a brief his­to­ry of ani­ma­tions on the web as well as out­lin­ing the pros and cons of each of the cur­rent­ly rec­om­mend­ed approach­es. More impor­tant­ly we’ve iden­ti­fied why ani­ma­tion is dif­fi­cult in the brows­er. We’ve also looked at some of the libraries avail­able to you if you want to build on the shoul­der of giants and final­ly we’ve gone through some of the more advanced APIs you might want to make use of when push­ing the browser’s boundaries.

Hope­ful­ly this has helped untan­gle ani­ma­tions in your mind and giv­en you a sol­id foot­ing going forward.



CSS Tran­si­tions and Animations

Web Ani­ma­tions

If you made it this far, you may also like

So you want an app, eh?


Welcome to hell