Bounce.js —

Joel Besada
Mar 5, 2015 · 5 min read

Smarter Keyframes

I recently pushed an update to Bouncejs.com to reduce the number of keyframes that the web tool generates. The update shaves off more than half of the exported CSS in most cases. By being smarter about where to place the keyframes, this was achieved without sacrificing the precision or smoothness of the animation.

Reducing the number of keyframes turned out to be a fun little problem to solve, so I decided to write this blog post to walk you through my solution.

A primer on @keyframes

For those not familiar with the @keyframes CSS rule, it’s a way for CSS authors to control the intermediate steps in an animation sequence. Each keyframe is defined with a percentage at which its assigned CSS rules should be applied during the animation. Here’s a simple example:

@keyframes move {
0% { left: 0px; }
50% { left: 150px; }
100% { left: 0px; }
}

The above keyframes describe a movement going from 0 to 150 pixels to the left during the first half of the animation, and then proceeding back to its original position during the second half. The above animation can be applied to an element like this:

.element {
animation: move 1s linear;
}

This will move the element along the defined path at a constant pace (linearly), reaching its end point after 1 second. The browser will do its best to animate this motion with 60 frames per second.

Translating an Easing Curve to Keyframes

The Bounce.js web tool generates these @keyframes blocks that the user can copy and paste into their own CSS code. The keyframes are based on a simplified model of spring physics, which makes the animated element bounce back and forth before settling in on its final position. Here’s an example curve that describes such a motion, with the y-axis representing the position and the x-axis representing time:

To translate this kind of motion into a set of keyframes, Bounce.js would previously simply input 30 evenly distributed keys for each second that the animation was defined to run for. Here’s how it would look given that the animation duration was set to 1 second:

@keyframes animation {
0% { ... }
3.33% { ... }
6.66% { ... }
9.99% { ... }
/* Etc.. */
}

To animate this smoothly, the browser will look at the 30 keyframes that it is given, and interpolate linearly between those to figure out the points in between. In the image above, you can imagine this as just drawing a straight line between each red key point along the path. This approximates the curve close enough to get a nice smoothness, but you’ll notice that you could easily be more efficient with how you’re placing out the key points to describe the shape of the curve.

Optimizing

So, let’s start from scratch and think about where our most important points on our curve is. Since the curve is describing an oscillating motion, we should probably start out with marking the turning points on the curve:

While these points are enough to describe the number of bounces during the motion, the resulting animation will still feel very stiff and unpleasant. To get a smoother result, we need to add in more key points in between these. To figure out where we need to place more points, we need something to measure how close our approximation is to the real curve. A simple way of doing this is to calculate the area between the line segments and the curve:

We see right away that the area A between our first and second key points is way too large. To make it smaller, let’s add a new point right in the horizontal middle of these two points:

We see that this has effectively reduced the area, so let’s keep doing this for each pair of subsequent key points (taking the newly inserted points into consideration as well) until we’ve reduced all areas below a threshold we feel comfortable with:

Now we have an approximation that is much closer to the original curve, while still being efficient with the number of key points we’ve placed. And that’s all there is to it!

Conclusion

With this approach, I was able to reduce the number of keyframes on an animation with a default bounce easing from 30 keyframes to 10. This reduces a lot of CSS bloat if you are using the Bounce.js tool to export animations into your web projects, which addresses something that many have been concerned about.

There are probably way smarter approaches out there to solving this problem, but I found my simple solution to be good enough for what I wanted to achieve. If you can suggest or implement a better algorithm, I would love to see your contribution in the Bounce.js GitHub repository!

Tictail - Behind the Scenes

Thoughts on product development from the people of Tictail.

Thanks to Alex Michael and Jonas Oscarsson

Joel Besada

Written by

Frontend tech lead at @tictail.

Tictail  -  Behind the Scenes

Thoughts on product development from the people of Tictail.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade