Sebastian Gomez
All About CSS Animations
CSS animations are a set of tools that let us show content in a more dynamic and eye catching way for the people who use our website. Their main advantage over transitions is that they give us more granular control over the animation states through @keyframes. Let's look at an example of a simple animation in shorthand form:
.animated-thing {
animation: black-to-white 1s linear 1;
}Now let's look at the same animation in longhand form:
.animated-thing {
animation-name: black-to-white;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: 1;
animation-delay: 0s;
}The animation name refers to its matching keyframe. We can define @keyframes in CSS as the waypoints of an animation, that is, they let us define what values the properties we're animating should reach along the sequence. This gives us more specific control over the intermediate steps.
@keyframes black-to-white {
0% {
}
25%,
35% {
}
100% {
}
}You can also use shorthand forms:
@keyframes black-to-white {
from {
}
to {
}
}You can also use more than one keyframe at a time by separating the different animations with commas:
.animated-thing {
animation: black-to-white 1s linear 1, black-to-red 2s ease-out infinite 2s;
}One thing to keep in mind is that animation names cannot start with numbers or special characters. For example, "1st animation" would not be a valid animation name.
Sprite animation using CSS
Sprite animation is one of the more advanced techniques you can achieve with CSS. First we need to understand what a sprite is: a sprite is a single graphic image that is incorporated into a larger scene so that it appears to be part of it.
Here we see an example of a sprite sheet for a character. Each movement sequence takes up the same space as the others and, by playing them together at a certain speed, we get to "perceive" that there is movement in the character.
To make an effective sprite animation in CSS, animation-timing-function: linear, ease-in, ease-out, ease-in-out or cubic-bezier are not enough, because in every case you'd see the frames sliding from one side to the other. That is where the steps(x) function comes in, where x is the number of sprites in your sprite sheet. steps(x) divides a block of keyframes into x equal steps and then jumps between them. Let's look at an example of how to animate the character from the sprite sheet above:
.walking-front {
background: url(/images/sprites/character-walk.png) 0 0 no-repeat;
animation: walking-front 1s steps(4) infinite;
height: 48px;
width: 32px;
margin: 50px auto 0;
}
@keyframes walking-front {
0% {
background-position: 0 0;
}
100% {
background-position: -128px 0;
}
}Note: serve the sprite sheet from your own domain over HTTPS. The original example linked to an external image over http://, which today is usually dead and, on top of that, the browser would block it as mixed content on a site served over HTTPS. Host the image yourself or use a properly licensed asset.
As a best practice, it's suggested not to write the 0% when the initial state matches the image's, so a reduced version could look like this:
@keyframes walking-front {
100% {
background-position: -128px 0;
}
}There are also three important properties you should know when creating CSS animations. Unlike the 0%, 100%, etc. steps, these properties do not go inside @keyframes: they apply to the element, alongside the other animation-* properties (such as animation-name). They are:
.animated-thing {
/* Controls which state is kept when the animation ends.
forwards keeps the final state; backwards applies the initial
one during the delay; both combines both; none is the default. */
animation-fill-mode: forwards;
/* Controls whether the animation is running or paused. */
animation-play-state: running;
/* Controls the direction of the animation. With alternate, an infinite
animation goes back and forth indefinitely. */
animation-direction: alternate;
}You can control the state of an animation with JavaScript using the following events (listeners):
animationstartanimationendanimationiteration
The following examples show a set of sprite animations with different properties. You can play with them to see their differences:
- Sprite with steps, example 1
- Sprite with steps, example 2
- Sprite with steps, example 3
- Sprite with steps, example 4
Personally I follow Rachel Nabors (@rachelnabors), one of my great heroes in the world of animation.
Best practices and considerations
Finally, a few considerations about animations:
- Animations can loop infinitely.
- They start automatically, they don't need a trigger like transitions do.
- They can alternate between the end and start states.
- They can group several properties together.
- Use animations to signal that an element changes direction, state, or momentum.
"Animations in", or entrance animations, are easier than exit animations (animation out). That's why you'll see thousands of sites with entrance animations that, once they finish, rarely return gracefully to their original state.
There are three types of animations:
- Supplemental Animations: those that are not related to the initial information.
- Decorative Animations: they only add decoration, and you should never have more than one.
- Stateful Animations: the core of your animation, animations over important content or over a call to action, highlighting details the person should not overlook.
Performance
If you're having performance problems with your animations, or you simply want to optimize the flow, the recommended property today is will-change. It intentionally tells the browser which property you're going to animate so it can prepare compositing ahead of time:
.animated-thing {
will-change: transform;
}Note: use it sparingly, only on the elements you'll really animate, because reserving compositing layers has a memory cost. The old advice was to use the transform: translateZ(0) (or translate3d(0, 0, 0)) trick to force hardware acceleration instead of leaving the work to the browser. Today will-change is the modern, intentional option, and translateZ(0) remains only as a legacy fallback for very old browsers.
Suggested exercises
- Create a simple animation in shorthand form and rewrite it in longhand form. Verify that both produce the same result.
- Animate your own sprite sheet with
steps()and experiment by changing the number of steps to see how it affects the movement. - Use
animation-direction: alternateon an infinite animation and add ananimationiterationlistener that counts the repetitions in the console.
Summary
- CSS animations are built on
@keyframesand theanimation-*properties, which apply to the element, not inside@keyframes. - The
steps()function is key to animating sprite sheets convincingly. - To optimize performance, lead with
will-change: transformand keeptranslateZ(0)as a legacy fallback.
That's all, I hope this post is useful to you and that you can apply it to a project you have in mind, or that it simply helped you understand the nature of CSS animations. Leave me a comment if you managed to implement it, if you want to add another feature, or if you have any questions. And remember, if you liked it, you can also share it using the social links below.
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.