A web design and development studio

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 pngs jpgs 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 at 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 are native 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 can use the animation-play-state CSS 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, WE WANT CONTROL.

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 behav­iour.

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 newish API 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

By the grace of god, we have launched two sites successfully in two days

Feral otis vokyl launch

It was close, but our faith in the natural goodness of the world has been restored