Generating CSS Keyframe animations with JavaScript for fun and profit

Yup. You can describe animations in JavaScript and generates CSS strings of @keyframe animations and insert them at runtime using <style> tags.

Sounds a bit wacky right? Why would you do this!?!

Well... because it’s fun, of course!

Here’s what I built with these techniques: SEE THE DEMO

We wanted a little menu we could add to any of our static sites, or open source project sites or wherever, really, just by dropping in a <script> tag.

I’m not endorsing this technique, per sé, but I think it might have its place for certain problems. Anyway, thought I’d write this up a bit to share what I learned.

First of all, why?!

It’s generally known that you have two options for animations:

  1. Writing CSS Keyframes statically once in your CSS
  2. Creating an animation loop in JavaScript, typically using requestAnimationFrame and calculating values in JS, applying them as element.style properties for the duration of your animation.

The CSS Keyframes technique is generally considered to be better performing, because you can offload things to the GPU. Especially if you’re animating CSS properties that are considered cheap to animate. But, it has some limitations:

  1. You have to know your animation values ahead of time.
  2. They’re kind of a pain to write, especially without CSS preprocessors that let you do loops, etc. It also requires that you do prefixed versions for ye olde iOS: -webkit-keyframe.
  3. They can a bit tricky to control and compose and observe state of these animations. Chris Coyier wrote a great post about how to do this.

If you need dynamic values requestAnimationFrame is the other option.

This is kind of a pain as well. Because you have to manage the life-cycle of the animation loop, simply leaving it running drains your battery.

There is actually a third option

If we generate CSS keyframe animations with JS, then we get a bit of the benefits of both.

  1. Offload to GPU
  2. Describe and get some limited control of animations in JS
  3. Can create dynamic values
  4. Doesn’t require running a requestAnimationFrame loop.
  5. Easier to chain and compose

I Googled a bit and didn’t find anything on npm that really did this and was having fun so I made a little library that lets me do this:

var animations = require(var animations = require('create-keyframe-animation')

// this creates the animation above
animations.registerAnimation({
  name: 'move',
  // the actual animation changes
  // these are x, y coordinates
	// there are other alternatives
  // for describing these too.	
  animation: [
    [0, 0], 
    [1, 1]
  ]
})

Now I’ve got an animation named move registered and I can run it on any element or elements I want like this:

var elements = document.querySelectorAll('.dot')
animations.runAnimation(elements, 'move')

Running an animation also returns a Promise so you can chain them pretty easily:

animations.runAnimation(el, 'stop')
  .then(function () {  
    return animations.runAnimation(el, 'drop')
  })
  .then(function () {  
    return animations.runAnimation(el, 'roll')
  })
  .then(function () {  
    console.log("Done!")
    console.log("You’ve stopped, dropped, and rolled.")
    console.log("You’re probably no longer on fire.")
  })
  .catch(function (err) {  
	  console.log("oww! fire’s hot!")
  })

You can read more in the readme.

A few things I learned

  1. If you’re running a CSS animation with a single iteration it will jump back to its original position when done unless you set animation-fill-mode to forwards or both.
  2. Removing the style.animation property will also revert the element to where it was before you started the animation.
  3. Reading the position of an element that has been moved with a translate transform can be tricky. In order to do it you have to use getComputedStyle and read the transform (or webkitTransform in safari. But that will give you a transform [matrix] which isn’t super easy to parse/read. I published a little module that uses a hacky method to extract the x, y position of a translated element from that matrix.
  4. style.transform and style.animation don’t play nicely together. If for example, you’ve got a transform, setting the animation property that animates that same property won’t do anything.
  5. Prefixes galore. Animation properties and transform properties are all prefixed in Webkit (iOS). Animation and transition end events are also prefixed for iOS. This module can help listen for those.
  6. If you played with the demo dot on your phone you may have noticed once you drag the dot you can toss it around and then tilt your phone to move and bounce the dot around with “gravity”. Note that this portion is done with requestAnimationFrame not the CSS technique. But it added a fun element and took surprisingly little code. Many mobile browsers will give you an ondevicemotion event that will include event.accelerationIncludingGravity which basically does the gravity math for you. I stumbled across this post on Alberto Sarullo’s blog and took his awesome 20 LOC demo switched it to use requestAnimationFrame and animating transform instead of top and left and was amazed and how well it worked. Hilariously, I discovered that Android and iOS have differing opinions on which way is up and give inverted values for acceleration. So I needed to do this to teach them which way was up:
window.addEventListener('device motion', (e) => {
  // iOS and android do opposite values :-/
  let modifier = /iPad|iPhone|iPod/.test( navigator.userAgent ) ? 1 : -1
  d.ax = (e.accelerationIncludingGravity.x * 5) * modifier
  d.ay = (e.accelerationIncludingGravity.y * 5) * modifier
})

Resources

After I had already written my little lib, I tweeted this:

Dynamically generating CSS keyframe animations with JS in the browser. Good idea? Or bad idea?

— Henrik Joreteg (@HenrikJoreteg) July 7, 2015

I discovered, unsurprisingly, that I wasn’t the first to try this.

Jeremy Kahn has written about and used this technique in his rekapi animation library.

amo.js also uses these techniques, and seems quite cool.

Other great people to follow who do web animation stuff GreenSock does amazing things. Check out the GSAP demos.

If you’ve seen any of Stripe’s sweet UI animations, Benjamin De Cock is probably more than partially to blame for that awesomeness.

If you want to understand the challenges of doing great, fluid animation on the web, check out Cheng Lou’s recent talk from React Europe. He’s built some awesome stuff for doing efficient spring dynamics inside react apps in his react-motion lib. Check out this chat heads demo or this one or this one.

I’m also a big fan of Impulse for simple JS physics simulations (also uses spring dynamics). There are some really impressive demos there too.

Now what?

I’ve sunk enough time into this idea already and have other things to do :) But I thought I’d share what I learned in case it’s useful to anyone.

See ya on the interwebz. I’m @HenrikJoreteg on twitter. /me waves

You might also enjoy reading:

Blog Archives: