CSS Animation: De-Animating the Undead Horde
What is Animation and Why
Animation, for the purposes of this book, is the change of CSS declaration values over time—and/or punching a zombie in the corpus callosum. Just like the corpus callosum, animation bridges the gap between artistic and technical skills.
Animation can take many forms, from character animation to interface animation, from visual movement to color change. You can animate just about any CSS property that has a mathematical representation, but how fast or slow that happens or whether your ballistic elements go in a straight line or arc gracefully into the zombie horde is an art form unto itself.
The best web animations enhance a user’s experience of the website, rather than solely providing a “cool” factor or pushing the technology. (I take that back. The very best animations do or build toward all three.) That said, if the addition of animation ever deteriorates the user experience or adds frustration, then you need to either remove it entirely or remove the deleterious effect. Even if it’s awesome, if it annoys users, it’s not worth including.
Necessary Zombie-Smacking Skills
This book assumes a rudimentary knowledge of HTML and CSS. If you don't have that yet, many wonderful, though zombie-less, tutorials are available online, or you can pick up a copy of A Beginners Guide to Learning HTML (or How to Smack a Zombie Upside the Web Development) and/or Beginner CSS: Like Putting Lipstick on a Zombie.
Roadmap to the Resistance
There are a few skills needed to build animations/stop those dead from being reanimated. We’ll look at transitions, transforms and keyframes before we get to the animations themselves. So if it feels like we’re taking a left turn into the urban wilds of zombie territory, don’t worry we’ll bring it all together by the end.
Transitioning to Zombiehood
The place to start in CSS animation is with transitions. A zombie infection transitions a property from one state (human) to a second state (zombie). In CSS, transitions can generally animate any mathematical value, such as a width or even a color.
The easiest way to start working with animations/transitions is to have a clear trigger for when the animation/transition starts and stops. When working on a desktop, the easiest choice is a mouseover or hover effect. The effect is easily triggered and can be triggered as often as you like to make sure it's working properly and/or taking zombies out at the rate you anticipated.
.zombie { background-color: yellowgreen;}.zombie:hover { background-color: darkred; transition: 2s;}
You can use a checkbox input element and the checked pseudo-class if you are working on a mobile device, or even on a desktop if you prefer this method. When you want to initiate the transition, just tap/click the checkbox.
.zombie { background-color: yellowgreen;}.toggle-checkbox:checked + .zombie { background-color: darkred; transition: 2s;}
Codepen Demo:
Note
To transition from the hover state back to the original put a transition on the original selector. It can be the same as or different from the original to hover transition.
Night of the living tip
For simple transitions the adjacent operator (+) is a great choice. As we get more complex the need for additional elements makes putting the checkbox directly next to the element you want to transition/animate harder and harder. So as we go you can use the sibling operator (~) to create this same trigger. The only requirement would be that the checkbox is before the animated element in the HTML and either a sibling or an aunt/uncle (including any flavor of great aunt/uncle) of the element you wish to animate.
You can also transition some properties without transitioning all properties.
.zombie { background-color: yellowgreen; color: black;}.zombie:hover, .toggle-checkbox:checked + .zombie { background-color: darkred; color: white; transition: 2s background-color;}
You can also transition properties separately.
.zombie { background-color: yellowgreen; color: black;}.zombie:hover, .toggle-checkbox:checked + .zombie { background-color: darkred; color: white; transition: 2s background-color, 3s color; }
Codepen Demo:
Night of the living tip
Transition is the shorthand property for setting several things at once. You can also set the property to be transitioned directly with transition-property, set the length of time with transition-duration, set a delay for how long to wait before the transition starts with transition-delay, and set a timing function with transition-timing-function (we’ll talk about timing functions in detail later, when we get to the animation property).
On Points and Cartesian Zombies
Before we get into transforms in the next section, it's important to know that they operate on a three-dimensional Cartesian graph. The simplified version of what that means is that changing an X value will move/distort/scale along a horizontal access. Y values govern the vertical access, and Z values govern an axis that's perpendicular to the plane of the screen (i.e., shooting out at you at a ninety-degree angle to the screen, like a human resistance fighter who shoots his gun up at the zombie birds circling above the battlefield).
Typically, for movement, when X and Y are both 0, you’re at the top left. Positive numbers will move the element down (Y) and right (X); negative numbers will move the element up (Y) and left (X). For Z values, a positive number will move toward the viewer (you/zombie birds) and a negative number will move away from the viewer and deeper into the screen (toward zombie moles).
Transforming Zombies
While transforms aren’t required for animation, and have uses outside of animation, they are the most efficient way of moving around and/or distorting an element in an animation. Most of the other properties that can move elements around will cause bigger performance hits because the browser must do more work to calculate the result (e.g., animating margin/padding etc.).
Zombie Origins
The transform origin of the element is the point around which it’s rotated, scaled, or skewed. You can change it by using the transform-origin property. An HTML element’s transform origin defaults to its center point. To change the origin to the top right, you would write:
transform-origin: top right;
The particular function you use will determine how the element is moved or distorted.
Night of the living tip
To be transformed, an element must utilize the CSS Box model—that is, it must be a flow element or be set to some flavor of display: block (display: grid and display: flex count too, but any that include "inline" do not).
Zombie Translation
Translate(): allows two values. The first is X, and the second is Y. It moves the element to the new X and Y location.
.zombie1:hover, .toggle-checkbox:checked + .zombie1 { transform: translate( 5em, -2em); transition: 2s;}
TranslateX(): takes a single value and only moves horizontally. It is useful if you don't want to move in any other direction.
.zombie2:hover, .toggle-checkbox:checked + .zombie2 { transform: translateX(5em); transition: 2s;}
TranslateY(): takes a single value and only moves vertically. It is useful if you don't want to move in any other direction.
.toggle-checkbox:checked + .zombie3 { transform: translateY(-5em); transition: 2s;}
TranslateZ(): takes a single value and only moves in the third dimension (either out of the screen or deeper into the screen). It is useful if you don't want to move in any other direction. Note: a perspective needs to be set for the translation to be visible. We’ll discuss the perspective function a little later.
.zombie4:hover, .toggle-checkbox:checked + .zombie4 { transform: perspective(500px) translateZ(-200px); transition: 2s;}
Translate3D(): allows you to set all three values at once in a comma-separated list, ordered as X, Y, and Z.
.zombie5:hover, .toggle-checkbox:checked + .zombie5 { transform: perspective(500px) translate3D(15em, 5em, -400px); transition: 2s;}
Night of the living tip
Three dimensional transforms by default do not seem to do anything. We'll talk about perspective later, but for now know that it's included on 3D transforms to make the changes visible.
Codepen Demo:
The Statistics Skewed Toward the Undead
Skew(): distorts the shape of an element in two directions: horizontally and vertically. If you started with a square, skew would turn it into a rhombus (not a zombus), and it would turn a rectangle into a parallelogram. It uses a comma-separated list for the degree of skew. The first number is horizontal skew, and the second number is vertical skew.
.zombie1:hover, .toggle-checkbox:checked + .zombie1 { transform: skew( 20deg, -40deg); transition: 2s;}
SkewX(): distorts the element horizontally only.
.zombie2:hover, .toggle-checkbox:checked + .zombie1 { transform: skewX( -40deg); transition: 2s;}
SkewY(): distorts the element vertically only.
.zombie3:hover, .toggle-checkbox:checked + .zombie1 { transform: skewY( -20deg ); transition: 2s;}
Codepen Demo:
<
Scaling up the Horde
Scale(): can take a singular value, a multiplier. A scale of 1 makes no changes, under 1 will shrink the element, and over 1 will enlarge it.
.zombie1:hover, .toggle-checkbox:checked + .zombie1 { transform: scale( 0.5 ); transition: 2s;}
ScaleX(): scales horizontally only.
.zombie2:hover, .toggle-checkbox:checked + .zombie2 { transform: scaleX( 1.5 ); transition: 2s;}
ScaleY(): scales only vertically.
.zombie3:hover, .toggle-checkbox:checked + .zombie3 { transform: scaleY( 1.5 ); transition: 2s;}
ScaleZ(): scales only in the third direction—that is, toward or away from the viewer. Again, perspective is required for the results to be visible to the user.
.zombie4, .zombie4a { transform: perspective(5000px);}.zombie4:hover, .toggle-checkbox:checked + .zombie4 { transition: 2s; transform: perspective(5000px) scaleZ(5) rotateY(30deg);}
Scale3d(): allows scaling in all directions at the same time.
.zombie5, .zombie5a { transform: perspective(5000px); }.zombie5:hover, .toggle-checkbox:checked + .zombie5 { transform: perspective(5000px) scale3d(1.6, 1.2, 1.5 ) rotateY(40deg); transition: 2s;}
Codepen Demo:
Slowly Rotating Braaains on a Spit
Rotate(): allows you to rotate an element. It takes one value of an angle (functionally, it is the same as rotateZ).
.zombie1:hover, .toggle-checkbox:checked + .zombie1 { transform: rotate(67deg); transition: 2s;}
RotateX(): rotation around the X axis.
.zombie2:hover, .toggle-checkbox:checked + .zombie2 { transform: rotateX(67deg); transition: 2s;}
RotateY(): rotation around the Y axis.
.zombie3:hover, .toggle-checkbox:checked + .zombie3 { transform: rotateY(67deg); transition: 2s;}
RotateZ(): rotation around the Z axis.
.zombie4:hover, .toggle-checkbox:checked + .zombie4 { transform: rotateZ(67deg); transition: 2s;}
Rotate3d(): rotates an element around a single axis, defined by an [X, Y, Z] vector. The first three arguments are numbers (X, Y, and Z), and the final number is an angle.
.zombie5:hover, .toggle-checkbox:checked + .zombie5 { transform: rotate3d(-1, 0.5, 0.75, 67deg); transition: 2s;}
Codepen Demo:
Three Dimensional Zombies
We’ve already seen a bit of perspective’s zombie-stomping action when it helped us visually see how translateZ and scaleZ worked. (Perspective as a concept is what makes things that are further away appear smaller and things that are closer appear larger. Without perspective, we couldn’t see the movement on the Z axis because the elements wouldn’t get smaller or larger as they move away from us or towards us.)
Perspective(): sets the distance between the plane (where Z equals 0) and the user, so that we can show 3D perspective. This is set on the current element and differs from the CSS perspective and perspective-origin properties because those are set on the parent element.
.zombie4:hover, .toggle-checkbox:checked + .zombie4 { transform: perspective(500px) translateZ(-200px); transition: 2s;}
Perspective-origin: sets the vanishing point for the element. The default is the center of the element. This is set on the parent element.
.zombieparent{ perspective: 500px; perspective-origin: 200% 100%;}
Codepen Demo:
Two other properties that can come in handy when creating transformations in 3D space are transform-style and backface-visibility.
Transform-style: allows you to set whether an element’s children have their own 3D space—that is, they can intersect in 3D space with their parent (for which the value is “preserve-3d”)—or live within their parent’s 3D space and are unable to intersect with their parent (for which the value is “flat”).
.zombie1:hover, .toggle-checkbox:checked + .zombieparent .zombie1 { transform: rotateY(-10deg); transition: 2s; transform-style: preserve-3d;}
Backface-visibility: determines whether the back side of an element will be visible when the element is turned over in 3D space. If you turned letters 180 degrees around the Y-axis and the backface/rear side is visible, then the letters will appear as though they are in a mirror. If the backface is hidden, then those letters and the rear of the element will disappear once the element is turned past 90 degrees. Note: As of this writing, webkit browsers still need the webkit prefix to work properly.
.zombie2:hover, .toggle-checkbox:checked + .zombieparent .zombie2 { transform: perspective(500px) rotateY(180deg); transition: 2s;-webkit-backface-visibility: hidden; backface-visibility: hidden;}
Codepen Demo:
Night of the living tip
As seen above with perspective and rotateY, if you separate them with a space, you can do multiple transform functions at a time (e.g., a translation and a rotation). The order you put them in is very important, as the next transform function will take the result of the previous transform as its input. This means that if you translate and then rotate, you’ll have moved the element over, and then rotated it. If you rotate and then translate, the element and its X axis will rotate before it will be translated—in other words, it will be moved along an angle instead of right or left. This is best seen visually:
Codepen Demo:
HTML Demo: https://undead.institute/files/animation/009-order-of-undead-transformations.html
Apocalyptic Matrices
Matrix(): is the underlying function for all your transformations. By extension, it allows you to do multiple transformations on an element (It’s what the Mathematicians call a homogeneous 2D-transformation matrix, but you don’t need to understand that to use it. It’s also fine to skip matrix() and use the more user friendly transformations discussed earlier). Matrix() takes six values:
Matrix(a, b, c, d, tx, ty);
A, b, c, and d are all numbers that describe the transformation. A and d deal with scaling in the X and Y directions (a is X direction; d is Y direction), while b and c deal with skewing in the X and Y directions (b for X, and c for Y). You can also rotate an element through skewing—b and c should be opposite mirrors. In other words, if b is negative 1, then c should be positive 1. If b is 0.48361, then c should be -0.48361.
Last, tx and ty translate the element in the X and Y directions.
.zombie1:hover, .toggle-checkbox:checked + .zombie1 { transform: matrix(0.5, 0.48361, -0.48361, 0.5, 30, -20); transition: 2s;}
Matrix3d(): allows you to do transformations in 3D space and takes 16 values.
Night of the living tip
Matrix() is really just shorthand for a version of the matrix3D() function: matrix3d(a, b, 0, 0, c, d, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1).
.zombie2:hover, .toggle-checkbox:checked + .zombie2 { transform: matrix(1.2, 0.48361, -0.75, 0.45, 100, 20); transition: 2s;}
/*same as transform above*/
.zombie3:hover, .toggle-checkbox:checked + .zombie3 { transform: matrix3d(1.2, 0.48361, 0, 0, -0.75, 0.45, 0, 0, 0, 0, 1, 0, 100, 20, 0 , 1); transition: 2s;}
Feel free to use matrix() and matrix3d() as much or as little as you want.
Codepen Demo:
Zombastic Keyframes
While transition is great for simple animations that require a trigger to start, they, like zombie math (1 brain + 2 brain = braaains), they are very hard to use for complex animations. That’s where keyframes and the CSS animation property come in to save the human race.
Keyframes are the points where an element moves from and to. They are the positions that define how the movement happens—the key or most important frames. You can set just two frames for an animation, or you can set a hundred or a thousand or however many you need to get the motion you want.
Night of the living tip
It's generally best to use as few keyframes as possible to get your desired result. Fewer keyframes are easier to manage and easier to update if you make a big change. That said, you need enough keyframes to get the movement or change that you want, so use as many as you need.
Keyframes require a name (this will be the name you use in animation-name when you apply it to an element). Then you have a list of rules for what should change and when. If you only have two keyframes (a start and an end point) then you can use to: and from: signifiers. Otherwise, you can use percentages.
Two frames:
@keyframes zombie-test-one {from { background-color: brown; } to { background-color: yellowgreen;}}
More frames in a loop:
@keyframes zombie-test-two {0% { background-color: brown; }50% { background-color: yellowgreen;}100% { background-color: brown; }}
Codepen Demo:
Night of the living tip
If you're writing an animation that loops, the easiest way to start is to set 0% and 100% to the same values. That way, the animation ends where it started, and you won't have a jump between the end of one run of the animation and the beginning of the next animation.
CSS Zombie Re-Animation Property
Animation-name: the name of the animation (i.e., the name of the keyframes you will use on this element).
Animation-duration: sets how long the animation should last. Do you want the animation to take five seconds or ten minutes?
Animation-delay: sets how long the animation will wait before starting. Note: it will only wait once, even if you repeat the animation. Also, you can set a negative number to have the animation “start” before the page loads. This is particularly useful to offset similar animations so that they aren't all starting at exactly the same time.
.zombie { animation-name: undead; animation-duration: 2s; animation-delay: 2s;}
Codepen Demo:
Animation-iteration-count: sets how many times the animation will run: 1? 2? 17? Infinite?
Animation-direction:
Normal: runs the animation in the normal direction (start to end, or 0% to 100%). This is the default.
Reverse: runs the animation in reverse (end to start, or 100% to 0%)—for example, the zombie shuffles backward.
Alternate: runs the animation normally, and then runs it in reverse (start to end to start, or 0% to 100% to 0%). The zombie takes two steps forward, then two steps back.
Alternate-reverse: runs the animation in reverse, and then runs it normally (end to start to end, or 100% to 0% to 100%).
Animation-play-state: shows whether an animation is paused or running. In this way, an animation can be paused and then started again from the paused position, rather than starting it over.
.zombie { animation-name: undead; animation-duration: 2s; animation-direction: normal; animation-iteration-count: 5; animation-play-state: paused;}
Codepen Demo:
Animation-timing-function: tells the browser how much acceleration or deceleration to use.
Ease: the animation starts slowly, ramps up to full speed, and then slows. This is the default.
Linear: moves the animation with constant velocity.
Ease-in: the animation slowly ramps up, and then goes full speed through to the end.
Ease-out: the animation starts at a constant velocity, and then slows down as it reaches the end point.
Ease-in-out: starts the animation slowly, ramps up the speed, and then slows down again. It is similar to ease, but with a more intense effect.
Steps(interval, start or end): interval refers to the number of intervals in the function to be stepped. The second, optional, term is either start or end and determines where the value change happens—at the start or the end of the interval.
Step-start: a shortcut for steps(1, start)
Step-end: a shortcut for steps(1, end)
Cubic-bezier(n, n, n, n): allows you to set your own easing function. All four value should be between 0 and 1. You can also re-create the functions above using the cubic-bezier function. Ease is cubic-bezier(0.25, 0.1, 0,25, 1), and ease-in-out is cubic-bezier(0.42, 0, 0.58, 1).
.zombie { animation-name: undead; animation-duration: 2s; animation-iteration-count: infinite; animation-timing-function: ease;}
Codepen Demo: https://codepen.io/undeadinstitute/pen/gObjreg
Animation-fill-mode: determines how you leave and begin your animation and what styles are applied when.
None: animation returns to the first keyframe, acts as if no animation happened, and applies no styles to the element before (during an animation delay) or after animation completion.
Forwards: keeps the values from the last keyframe after the animation finishes.
Backwards: uses the values of the first keyframe during an animation delay
Both: the animation will do both backwards and forwards, showing the first keyframe during an animation delay and leaving the last keyframe in place after the animation finishes.
.zombie {animation-name: undead;animation-duration: 2s;animation-fill-mode: forwards;animation-delay: 2s;}
Night of the living tip
The last frame and first frame of an animation will depend on the animation-direction property. CSS considers this based on the play order i.e. what frames are played first or last. So if you run the animation normally the first frame will be 0% and the last frame 100%. If you run the same animation in reverse, the first frame would be 100% and the last frame 0%. If you alternate it, wherever the animation starts is the first frame and wherever it finishes is the last (thus, in this case, it will also depend on the animation-iteration-count).
Codepen Demo:
Re-Animation Principles
In 1981, Frank Thomas and Ollie Johnston, two titans of Disney’s animation studio, published a book called The Illusion of Life: Disney Animation. In that book, they set out twelve principles of animation, gleaned from their own experience as well as from their colleagues at Disney, reaching all the way back to 1930. These twelve principles were aimed at hand-drawn, two-dimensional character animation, but they often still apply, even when you're doing user-interface animations or other, non-character-based animation. The twelve principles of animation are: squash and stretch, anticipation, staging, straight-ahead action and pose to pose, follow-through and overlapping action, slow in and slow out, arc, secondary action, timing, exaggeration, solid drawing, and appeal. Let's look at how we can apply each principle to user-interface animation, as well as to character animation on the web.
Squash and Stretch
The easiest way to see this technique is with a zombie head. Since the zombie’s brain cavity is now mostly empty and the skull has degraded into a rubbery substance, once your animation code effortlessly removes the zombie's head from its shoulders, it will bounce like a ball as it hits the ground. If the round head just touches the ground and comes back up, it won't really feel like a true bounce. You need to squash and stretch the head. First the head flattens out as it hits the floor, and then goes from a horizontally flat (squashed) ball to a thinner ball that “leaps” up from the ground (stretch). Adding this kind of squash and stretch adds physicality to the movement and helps ground it in reality, even if that squash and stretch is more exaggerated than what would actually happen. Exaggerated reality feels more real to a user than unreal behavior does. How much and the way in which you exaggerate the motion can introduce character or personality to the object you're squashing and stretching (whether it’s for an interface or for a character). A ball that falls at normal speed, and then slowly bounces in an exaggerated manner will seem like a different character than a ball that only slightly squashes and stretches in a bounce.
Codepen Demo:
While user interfaces will probably have less use for squash and stretch than other kinds of animation, it's a good tool to have in your pocket to add a little life or reality to a UI movement.
Anticipation
Anticipation is movement that happens before the main movement. If you are going to throw a ball down with your hand (rather than just drop it), you will move it up before you throw it down. That's the anticipation. People and animals often make an opposite motion just before they perform the intended motion. It’s more than just about movement, it’s about creating an expectation for the audience and then fulfilling that expectation (e.g., by doing the same animation for similarly functioning elements). This is another thing that can add that special something to your user interface work because it mirrors real motion and/or satisfies expectations.
In this example, keep your eye on the cannon after you hit Fire!
Codepen Demo:
Staging
This refers to placement on the “stage.” In more traditional animation 2D, and even in 3D film and television animation, this is where and how you place the characters, props, and scenery in relation to the camera (i.e., the viewer).
If you have two people having a conversation, are they standing next to each other talking?
Are you shooting over the shoulder of one person?
Is one sitting down while the other stands?
Is one looking through a hole while the other is deep in a zombie’s nest?
All these lead to different feelings from the audience, even if the dialogue and actions are the same. A closeup or over-the-shoulder shot feels more intimate, while a shot where both characters are in the background puts the audience far away from them and the action.
If we add a third character, does that character enter from the right or left? Does he or she cross in front of the other characters, behind the characters, or repel down from an anti-zombie helicopter on top of one of the characters?
This example uses extreme staging to help communicate the "story"
Codepen Demo:
These spatial relations are also important in UI animation. How the other elements on the page move or don't move and how they move in relation to other elements can make the difference between a good user interface and a confusing, unusable one.
Straight Ahead Action and Pose to Pose
This encompasses two ways of animating. One way is “straight ahead,” meaning you draw every frame one after the other. The benefit is that the animation feels more fluid, and it can make action scenes feel more lifelike and exciting. The downside is that you don't have guard rails for keeping proportions accurate, and it can be harder to make certain that the character/object ends/arrives where you need it to.
The other way is “pose to pose,” where you set certain important poses or keyframes and animate between them. The benefit of this approach is that you always know exactly where you're coming from and where you're going, so it's much easier to keep proportions accurate and to line up characters and objects to where you need them to be. The downside is that the action can feel wooden and precomposed (i.e., without life).
With animation in CSS, we’re always going from keyframe to keyframe (i.e., pose to pose) because that's how animation works in CSS. If you want to create something that feels more lively or organic (and less undead) you can place your keyframes closer together to get that straight-ahead action feel.
Follow-Through and Overlapping Action
If you swing a wrecking ball at a zombie, the ball won't stop just because it hits the horde. The ball usually continues its swing even after connecting with a zombie face. This is follow-through. It's continuing a motion past the intent of the motion (e.g., hitting the target of the wrecking ball swing).
Codepen Demo:
Overlapping action is related. You don't want to wait for a full motion to finish before you start to animate some other part of the character or something else. In real life, zombies won’t wait until someone completely finishes swinging a ball at them to try to bite the person. Action that overlaps is more realistic.
In user interface (UI) animation, if you have elements that are chained together in some way, then follow-through and overlapping action can be important to help keep the animation feeling natural and real.
Slow In and Slow Out
This is really important for user interface animation. Most movements don't run at a constant speed. They’ll usually start off slow, accelerate to the midpoint of the animation, and then slow down as it comes to the end.
This floating brain slows as it reaches the top and bottom of its float height
Codepen Demo:
Arc
In real life, motion tends to follow an arc rather than a straight line. For instance, if when you swing that bat, it will arc because it’s connected to your hand. It would also make an arc if you threw it. It’s not connected to your arm anymore, but gravity will pull it closer to the ground as it goes along. Thus, most motions, particularly those in the air that are unimpeded by the ground, will occur in an arc. In UI animation, this can be useful if you are moving a button around or flinging out a menu or something similar. The action will look and feel more natural if it follows some sort of arc than if it’s sent along a straight line.
If you shoot a zombie out of a cannon it’ll arc toward the ground too.
Codepen Demo:
Secondary Action
This is animating motion that is related to but separate from the main action. It should also emphasize the main action rather than take away from it. For instance, if a zombie’s shuffling step is the main action then a secondary action would be its eyes lolling around or its arm movements. In UI animation, if you highlight an element by bringing it forward in space, a secondary action might be making the elements next to it recede in space or move to grayscale or slightly blur etc.
In the zombie cannonball animation above, take a look at the cannon as you fire as well as the smoke cloud. They are secondary actions that support the main action of firing the zombie.
Timing
Timing can be somewhat boiled down to frame rate—or, more specifically, the way you manage actions within frames. If a zombie takes five hundred frames to shuffle across the page, he will be moving slower than the human who takes two hundred frames to cross the page. This rule is really about the amount of movement that happens between frames. In user interface animation, this deals with how fast or slow an animation moves and whether the frame rate you’re using is appropriate for the movement you’re showing.
Exaggeration
Believe it or not, a faithful representation of natural movement will often seem stagnant and uninteresting when reproduced on a screen. Even when a movie or game uses motion capture to copy the actor’s movements, those movements need to be tweaked and exaggerated to seem more real on the screen. Perhaps it’s because our eye takes in more than we realize when we watch a person move, a fidelity that even our most realistic graphics don’t yet achieve, but whatever the reason, particularly when applied to character movement, exaggeration will help your animations feel more dynamic and fluid and interesting.
This exaggeration in character movement rarely helps in user interface animation, but it can still come into play and enhance your work. An exaggerated button move or an over-the-top animation can add a lot of character to the site and, depending on the site’s brand and the purpose of the page, it could endear the user to the site. Exaggeration, like so many of these principals, is a tool you can apply according to the situation. Exaggeration can increase character, comedy, or interest. But there are also times when subtlety, professionalism, and uniformity are important, and exaggeration is a poor tool for creating those kinds of experiences.
Solid Drawing
This is about underlying drawing ability. Motion is sort of another dimension of a drawing, so having solid drawing skills can really enhance the look and feel of an animation. Poor drawing can make good animation look bad. While this probably has the least direct correlation to user interface design, consider two things here. One: if you’re adding characters to your user interface, such as a zombie that shuffled across a toggle switch to show on and off or a horde that pushes out a submit button once the required form fields are filled in, you should have solid drawing skills for those parts, and it’s worth spending the time to make them look and feel just right. Two: understanding both the underlying HTML and any CSS that’s applied to the elements you’re animating (whether that CSS is animated or not) is a fundamental need for animation that looks great, functions great, and works consistently across browsers. How an HTML element like a form field works can change how the animation works and where and how a user uses that form field, so solid coding is also a principle of CSS animation.
Appeal
This can be summed up as whether the character is likable and interesting or not. Villains can have appeal, so it’s not truly likability, but whether the character interests us. Can we understand the characters? Have we seen someone like them before? Do they seem real? While this, once again, has more to do with character animation than user interface animation, it is still important to think about it when designing for an interface. Will the user feel playfully guided into an action or annoyingly forced into that same action? Sometimes the difference between those extremes is the appeal of the character or button or movement. The more appeal/interest/likability, the less the user feels forced, and the more the user feels helped by the character or button or thing.
This zombie might not fool you, but his awkwardness is endearing.
Codepen Demo:
Animated Undead Usability
“Flash,” in some circles, is a dirty zombie word (i.e., “It’s too Flash-y” or “I hate Flash screens”). To some extent, those people are right. they’ve seen a lot of bad animations and terrible site interactions built with Flash. The debacle that was a site needing a “Skip intro” button still gives me apocalyptic nightmares, but it wasn’t Flash’s fault that people used it poorly. It was an excellent animation tool whose fun-ness got in the way of its utility. While Flash has mostly gone away, just like “safety” after the apocalypse, there’s a lot we can learn from its demise and how to prevent CSS animations from meeting a similar fate.
Undead Performance
Performance is one of the biggest topics around web animation, because a slow site can result in the loss of membership, revenue, and/or traffic. Even if the site loads quickly, if it devolves into jaggy animations or pixelated graphics, it may be abandoned for less “flashy” but more solid code.
While you can animate just about any CSS property, not all properties are animated with the same efficiency. Animating the margin or padding of an element to move it around causes the browser to do an expensive rendering step called reflow. This requires the browser to do a lot more work verifying the visibility, layout, and visual relationship between every node (element) in the DOM (document object model, which is the structure the browser uses in the background to build and display the page). It’s like knowing a zombie has halitosis and checking every zombie in the horde to see if that made other zombies either lean in to get a good whiff or totally avoid the one who had bad breath.
A repaint is a far less expensive rendering task and is, thus, much preferred to a reflow. It happens when the change affects how the element looks but doesn’t affect its relationship with other elements. The transform functions, even though they often visually change where an element is, don’t officially change the element’s location in the DOM or its visual and layout relationship with other elements. Thus, you can check the halitosis of a single zombie rather than having to check its relationship with every other zombie in the horde. This is a lot faster.
Apocalyptically Blocking Behavior
You also need to look out for “blocking behavior”—that is, where all work in the browser stops while the browser downloads a large file or runs a script or does something else that prevents any other code/download from running/downloading.
Similarly, from a usability standpoint, if the animation blocks or delays a user from doing a task, such as moving to the next screen or inputting something, you’ll frustrate and annoy users. Ensure that long animations and/or ones that must complete before the user can perform a task either only happen once or can be turned off/stopped.
Horded Overload
It’s very easy to include too much animation that overwhelms or overloads users. If bright things are moving all over the screen, it can be difficult for users to understand what’s happening or to follow along with the things they need to know. Focus on what’s most important, and leave the flourishes to a minimum.
Some people will feel sick or become overwhelmed, no matter how little motion there is. Sometimes this is a medical condition, and sometimes it’s just a preference, but, either way, we should provide ways to let people choose how much motion they want to see. There is a CSS media query that can help with this called prefers-reduced-motion. This is a signal to the developer to remove all but the most necessary motion.
.jumping-spastic-horde{ animation: go-crazy 2s linear infinite;}@media (prefers-reduces-motion: reduce) { .jumping-spastic-horde { animation: none; }}
In this way, you can remove motion, and you can also set it to slower or decreased motion by using different keyframes or different animation property values.
If the browser doesn’t support prefers-reduced-motion, then the browser will just ignore it. The positive of this is that it won’t cause anything to break in older browsers. The negative is that users who prefer reduced motion could get a motion-filled experience if they use an older browser.
Hordes of office workers may not have a choice in the browser they use, or they must use a specific older browser for software the business created last millennium and just use that browser for everything. One way to ensure that those users get the best experience is to swap the default. Prefers-reduces-motion can have two values: reduce and no-preference. Reduce is self-explanatory. No-preference means the browser has not received information from the user. If no preference is set, we can then add in the animations. In this way, users who prefer or need reduced movement will be best served.
.zombie{ animation: none;}@media (prefers-reduces-motion: no-preference) { .zombie{ animation: follow-horde 2s linear infinite; }}
That said, if you are concerned about users not getting your animations because they have older browsers, you could always notify them of that. Have an element that holds a message to older browsers (in this case, browsers that don’t support prefers-reduced-motion). The message can read something like, “For a richer, more secure, and less zombie-filled experience, please update your browser.” The code below will display the message by default, but if the browser supports prefer-reduced-motion, then the message is set to display: none.
.zombie{ animation: none;}.old-browser-message { display: block;}@media (prefers-reduces-motion: no-preference) { .zombie{ animation: follow-horde 2s linear infinite; } .old-browser-message { display: none; }}@media (prefers-reduces-motion: reduce) { .zombie{ animation: none; } .old-browser-message { display: none; }}
The End?
While we’ve covered quite a bit about CSS animation, it’s very much both an art and a science. Knowing the skills and perfecting them are different things. Animation can take a lifetime (or even an undead eternity) to master, so keep practicing, keep learning, keep moving forward (or backward or in three-dimensional space), and keep showing the horde the depths of your humanity.
Undead Motion Test
While you can smack down zombies a thousand different ways, CSS animation is one of the most fun.
Let’s prove your knowledge. Complete the following animated zombie bombs. The provided codepen is a skeleton from which to start.
Codepen: https://codepen.io/undeadinstitute/pen/ExapweJ
HTML: https://undead.institute/files/animation/022-the-final-reanimation.html
- Use hover or a checkbox to initiate both a color and a background-color change for the first zombie.
- Move the second zombie in two different directions (i.e., diagonally).
- Move the third zombie noticeably in the Z direction.
- Change the fourth zombie using four transformations.
- Spin the fifth zombie on any axis and change the background-color at the same time.
- Using the sixth zombie, make (at least) two movements and change the easing function.
- Create an animation with the seventh zombie that has at least seven keyframes.
- Squash/stretch the eighth zombie.
- Use anticipation to animate the ninth zombie.
- Animate the tenth zombie any way you want, and use prefers-reduces-motion to prevent animation from bothering users.
- Run your code through both HTML and CSS validators.
Work through these on your own, and your solutions can (and probably should) vary from mine. Here’s how I completed the steps above:
Codepen: https://codepen.io/undeadinstitute/pen/bGNxbEe
HTML: https://undead.institute/files/animation/023-the-final-reanimation-answers.html
Got Questions?
Join the Undead Institute Facebook group and get answers. https://www.facebook.com/groups/490540551375786/
You Know what else kills Zombies?
Honest reviews. Please consider leaving one to help the fight against the apocalypse.
I’d also love to see where and how you’re reading or using the book. Twit me a pic at @storykaboom and/or use the hashtag #GetBraaains