Creating CSS animations may be about learning the syntax, but mastering a beautiful and intuitive-feeling animation requires a bit more nuance. Since animations command so much attention, it’s important to refine our code to get the timing right and debug things when they go wrong. After tackling this problem myself, I thought I’d collect some of the tools that exist to aid in this process.
Using Negative Delay Values
Say you have multiple animations running at the same time and you’d like them to stagger just a bit. You can use animation-delay
, but don’t necessarily want the viewer to visit the page and have some things not moving, waiting for their delay.
You can set animation-delay
to a negative number, and it will push the playhead back in time, so that all animations are running when the viewer shows up. This is particularly useful when the animations share the same keyframe values and are differentiated in movement only by the delay.
You can use this concept for debugging. Set animation-play-state: paused;
and then adjust the delay to different negative times. You’ll see the animation in different paused states along the tween.
.thing {
animation: move 2s linear infinite alternate;
animation-play-state: paused;
animation-delay: -1s;
}
Example:
See the Pen LVMMGZ by CSS-Tricks (@css-tricks) on CodePen.
In a fun demo, we see the two robotters hovering at a very slightly different time in order to feel a little more natural. We give the purple robotter a negative delay when we declare the hovering animation so that he’s moving when the viewer first sees the page.
.teal {
animation: hover 2s ease-in-out infinite both;
}
.purple {
animation: hover 2s -0.5s ease-in-out infinite both;
}
@keyframes hover {
50% {
transform: translateY(-4px);
}
}
See the Pen Robotter Love by Sarah Drasner (@sdras) on CodePen.
The Pains of Multiple Transform Values
For best possible performance, you should also be moving and changing things with transform
, and save yourself from the cost of repaints with margin, or top/left and the like. Paul Lewis has a great resource called CSS Triggers that break down these costs in an easy-to-see table. The gotcha here is that if you try to move things with multiple transforms, there are a number of problems.
One big problem is ordering. Transforms will not be applied simultaneously as one might expect, but rather, in an order of operation. The first operation done is the one furthest on the right, then inwards. For example, in the code below, the scale will be applied first, then the translate, then the rotate.
@keyframes foo {
to {
/* 3rd 2nd 1st */
transform: rotate(90deg) translateX(30px) scale(1.5);
}
}
In most situations, this is not ideal. It’s more likely that you’d rather all to happen concurrently. Furthermore, this becomes far more complex when you begin splitting the transforms into multiple keyframes with some values at the same time and some not, like so:
@keyframes foo {
30% {
transform: rotateY(360deg);
}
65% {
transform: translateY(-30px) rotateY(-360deg) scale(1.5);
}
90% {
transform: translateY(10px) scale(0.75);
}
}
This will lead to somewhat surprising, and less than ideal results. The answer, unfortunately, is often to use multiple nested <div>
s, applying a single translation to each, so that no conflicts arise.
See the Pen Show Order of Operations Transform Fail by Sarah Drasner (@sdras) on CodePen.
There are some alternatives, such as using matrix transforms (not intuitive to code by hand) or to use a JavaScript animation API such as GreenSock, where there is no ordering sequence for multiple transform interpolations.
The multiple div implementation can also help with troubling bugs with SVG. In Safari, you can’t declare opacity and transform in animation at the same time — one will fail. You can see the workaround in action in the first demo in this article.
In early August 2015, independent transform declarations have moved into Chrome Canary. This means we won’t have to worry about ordering much longer. You’ll be able to declare rotate
, translate
, and scale
separately.
DevTools Timing Helpers
Both Chrome and Firefox now ship with some tools specificially for helping work with animations. They offer a slider for controlling the speed, a pause button, and UI for working with the easing values. Slowing things way down, and seeing the animation at particular stopped points is pretty darn helpful for debugging CSS animations.
They both use Lea Verou’s cubic-bezier.com visualization and GUI. This is extraordinarily helpful, as you no longer have to do the back-and-forth from cubic-bezier.com to text editor to proofing.
These tools allow us to fine-tune our animations much more intuitively. Here’s a look at the UI offered in both:
Both Chrome and Firefox allows you to control the timing (speed up or slow down), and also manually scrub through the animation. More advanced timeline tools are coming in Chrome that can look at multiple elements at at time. That will be nice, as working with only a single element at a time in an animation is fairly big limiting factor.
One thing I have some trouble with is grabbing the element quickly enough if the animation is short-lived and fast. In those cases, I’ll usually set it to animation-iteration-count: infinite;
so that I can continue to play with it without having to fight time.
I also find it extremely helpful to slow the animation way down and then replay and adjust the timing in the browser with these tools. It allows you to pull apart each movement at a fundamental level and see how everything is interacting and what the motion progress looks like. If you refine it at that speed, when you adjust it to play faster, it will look much more masterful.
Debugging CSS Animation Events with JavaScript
If you’d like to figure out exactly where and when each animation is fired, you can use a little JavaScript to detect and alert you to when each event is occurring, by hooking into animationstart
, animationiteration
and animationend
.
Here’s a demo of that:
See the Pen Showing how to Debug Animation Play States in JavaScript by Sarah Drasner (@sdras) on CodePen.
Keep Keyframes Concise
I often see people declare the same property and value at the 0% keyframe and 100% keyframe. This is unnecessary and leads to bloated code. The browser will take the properties value as the initial and ending value by default.
Meaning this is overkill:
.element {
animation: animation-name 2s linear infinite;
}
@keyframes animation-name {
0% {
transform: translateX(200px);
}
50% {
transform: translateX(350px);
}
100% {
transform: translateX(200px);
}
}
It could be written like this:
.element {
transform: translateX(200px);
animation: animation-name 2s linear infinite;
}
@keyframes animation-name {
50% {
transform: translateX(350px);
}
}
DRY-ing Out Animations
Creating a beautiful and succinct animation usually means writing a very specific cubic-bezier()
easing function. A fine-tuned easing function works similarly to a company’s palette. You have your own specific branding and “voice” in your motion. If you’re using this all over a website, (and you should, for consistency) the easiest way to do so is to store one or two easing functions in a variable, just as we do with our palettes. SASS and other pre/post processors make that simple:
$smooth: cubic-bezier(0.17, 0.67, 0.48, 1.28);
.foo { animation: animation-name 3s $smooth; }
.bar { animation: animation-name 1s $smooth; }
When animating with CSS keyframes, we want as much help from the GPU as possible. That means that if you’re animating multiple objects, you want a way to easily prepare the DOM for the incoming movement and layerize the element. You can hardware accelerate a native DOM element (not SVG) with CSS by using a standard declaration block. Since we reuse that on all of the elements we’re animating, it makes sense to save some typing, and add this in using a mixin or extend:
@mixin accelerate($name) {
will-change: $name;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
.foo {
@include accelerate(transform);
}
Be careful. Offloading too many elements at once can cause an inverse effect and performance to tank. Most animations should be fine, but be aware of this if you’re using something like haml to generate tons of DOM elements.
Loops for Better Performance
Smashing Magazine recently published a great piece showing the work behind the fantastic project, Species in Pieces. In one particular section, the author goes into detail about how animating all of the pieces at one was causing performance problems. He states:
Imagine that you’re moving 30 objects at the same time; you are asking a lot of the browser, and it makes sense that this would create problems. If you have a speed of 0.199 seconds and a delay of 0.2 seconds on each object, you would fix the problem by moving only one object at a time. The fact that the same amount of total movement happens doesn’t matter: If the animation is done as a chain, performance is immediately improved by 30 times.
You can take advantage of Sass or other pre/post-processor for
loops for this kind of functionality. Here’s a pretty simple one that I wrote to loop through nth-child:
@for $i from 1 through $n {
&:nth-child(#{$i}) {
animation: loadIn 2s #{$i*0.11}s $easeOutSine forwards;
}
}
Not only that, but you can use them to stagger visual effects like color as well. (Hit replay to rerun the animation.)
See the Pen SASS for loops in CSS Animations by Sarah Drasner (@sdras) on CodePen.
Adjust Many Animations in Sequence
When you’re creating longer animations, the way to sequence multiple animations or events is usually to chain them together with progressive delays. So something like:
animation: foo 3s ease-in-out, bar 4s 3s ease-in-out, brainz 6s 7s ease-in-out;
But let’s say you’re making refinements, and you discover that the second count of the first animation should change. This affects the delays of everything following it, so let’s adjust our timing for each one after. No big deal.
animation: foo 2.5s ease-in-out, bar 4s 2.5s ease-in-out, brainz 6s 6.5s ease-in-out;
But now let’s add another animation and adjust the timing of the second one again (this kind of fine-tuning happens all the time when creating a really nice animation in production). Well, this is starting to become a little inefficient. If you do that 3 more times, it’s really inefficient.
Then imagine halfway through the animation two things have to fire at once so you have to keep consistent timing with two different properties and… well, you get it. That’s why anytime I get beyond three or four chained animations in a sequence, I usually switch to JavaScript. Personally, I like the GreenSock animation API because it has a very robust timeline functionality, but most JS animation will allow for you to easily stack animation without any recalculation which is definitely a workflow boon.
∞
Making an animation work well is so much more than just building it. Typically, it’s the editing, refining, and debugging that take a project from merely moving to a well-orchestrated and performant piece. Hopefully these tips put a few more tools in your toolbox and smooth some rough edges in your workflow.
Awesome article. A lot of really helpful tips and tricks in there.
I wonder if there are any tools out there for authoring CSS animations in a timeline-like manner? Something similar to tweens back in the Flash days, except, it spits out CSS animations?
I don’t think there is at this time, unfortunately. There are a few timeline animation tools like Edge Animate and Animatron that export out various flavours of JavaScript animation though. And there are some handy tools like bounce.js (http://bouncejs.com/) that help you create matrix transform animations visually.
Not for CSS, no, but exactly like in the flash days (because you might have used this for flash before: GreenSock has a JS Animation API with a really robust timeline functionality: http://greensock.com/timelinemax. Otherwise, bodymovin.js lets you make things in After Effects and export to JavaScript. https://github.com/bodymovin/bodymovin. Hope that helps!
Perfect opportunity to write a tool that exports CSS, then! (Or perhaps just an alternative exporter for an existing tool.)
Something like this maybe? http://cssanimate.com/
Hey look at that! Cooool. Nice tip.
Thank for the article, very clear and easy to read!
Thanks! Glad you found it helpful!
Amazing information in you article. New trics learn from you.
With css we can generate easily grapics and animation. Dev tools and loops are better for this. Good job. Thanks for share it.
http://www.discoverwebdesignmelbourne.com.au/
Nice article!
Relative to getting the help of the GPU, isn’t it total overkill to use
will-change
,backface-visibility
andtransform
properties at the same time?Good question! I typically don’t use will-change that much myself, just the other three, but I know others that swear by it. It would be worth a quick test to see if there are advantages to adding will-change in as well (I’ve run tests that show there are huge advantages to the other three- see https://css-tricks.com/weighing-svg-animation-techniques-benchmarks/) but should try again both with and without will-change, on divs. I would say test anything perf-related, personally.
Great article!
Regarding the multiple transform issue, it’s easily remedied by adding “none” to the keyframes with fewer transform values.
Demo:
http://codepen.io/brianknapp/pen/QbYboK
So! I was really excited when I saw your example, but after some really close comparison and (there’s a joke in here somewhere) debugging, your technique isn’t actually the same thing- there’s a small but discernable difference, and it’s a little inaccurate- a difference probably only someone like me notices- but if you’re trying to perfect something minute, it does make a difference. Here’s what I mean:
See the Pen Show Order of Operations Transform Fail by Sarah Drasner (@sdras) on CodePen.
But if I’m wrong about that, please let me know, I’m always trying to learn.
You’re right. After a lot of playing around with this, I’m not sure what none is actually doing besides smoothing out some of the states. It also chops off the last keyframe, which is no good :(
In fact, the transforms always are animated ‘simultaneously’ because there are no different transforms, but just one transform (just as the CSS syntax, where all transform functions make up just one value of the single
transform
property) with several components, animating through several intermediate values. The problem is, that missing values for the transform components mean their default values, not “unchanged”. I.e.is equivalent of
But if we replace these default values with manually interpolated ones, we can achieve almost the same result as with separate animations of differently transformed wrappers: http://codepen.io/SelenIT/pen/pJYOmY
So, in my opinion, saying that “transforms are applied one after another” is kind of oversimplification.
Hi SelenIT! Your pen is great, I think it’s the first one I’ve gotten that does demonstrate how to use one div for all the transform values, I’ll edit the post to include this solution. Fantastic.
One thing I will say is that writing this isn’t that intuitive- consider that you need to adjust just the scale from 1.5 to 1.75 at 60% instead of 65%. You then have to go recalculate all of the other values that normally would be interpolated for you. I’d say that that kind of workflow jam can really slow you down when animations get more complex or you’re trying to refine a few things at once. Still, you avoid multiple divs, so it really depends on what you’re optimizing for. Again, thanks for the input, I’m sure it will help people.