Chapter 10. CSS3: Transforms, Transitions, and Animations

On the desktop, most of the “animations” you have likely seen aren’t actually CSS animations, but rather Flash, Canvas, or JavaScript animations. On mobile devices, it is important to use CSS3 transitions, transforms, and animations to animate elements whenever possible instead of these other techniques.

So why not animate with Flash, JavaScript, or <canvas>? Flash is not supported on mobile iOS devices, and never will be. Adobe, the makers of Flash, discontinued development of the mobile Flash Player, with the last release being Flash Player 11.1 for Android and the BlackBerry PlayBook in late 2011.

Flash is still being developed for desktop, but with Flash not supported or installed on any new devices, you’ll be missing out on a huge chunk, if not all, of the mobile market. And, for those using older devices with browsers that support Flash, you’ll be draining their battery.

Similarly, animations done with JavaScript, when not hardware-accelerated, block the UI thread. This can make the rest of the application, if not the animation itself, choppy, nonresponsive, a memory hog, and a battery drainer.

On mobile, CSS animations are an awesome alternative to these other technologies. Browsers are optimized to handle CSS, so you lose less memory, CPU, and battery.[67] And, animations, transforms, and transitions are supported on all modern smartphone browsers, so there is no reason to not use them.

Well, there is. An important note about transitions, transforms, and animations: just because you can, doesn’t mean you should. Use instructional animations to show procedures or tasks that are hard to describe with static pictures. Accompany the instructions with text that explains the animation.

Animation can be used to draw attention to an element on the page, such as a form submission error message, a success message on a one page application, or an update to the page that is important that the user may not otherwise have noticed.

Don’t animate unless you want to draw attention, and don’t animate elements that need to be read or interacted with. Games, of course, are an exception to these rules.

These features of CSS are very captivating when used wisely and sparingly. Don’t overuse, unless you’re using these features in gaming, or your site will be reminiscent of a Geocities site from the 1990s.

CSS Transitions

Transitions allow CSS properties to change from one value to another over a period of time. If you’ve ever used :hover to change the color of a link, you’ve used a transition, but likely a transition of zero milliseconds in duration. With CSS transitions, you can make that color change, and a whole lot of other properties change, over a period of time.

CSS transitions apply to any change from one state to another state. The transition shorthand property is made up of four properties: (1) transition-property, which defines which properties are affected, (2) transition-duration, which sets the during of the transition effect, (3) the transition-timing-function, which delineates how the timing will accelerate, decelerate, or otherwise change during the transition, and (4) the transition-delay, which sets how long to wait before starting the transition after the transition is initiated.

To create a transition, set the styles of the element you want to transition. In the properties of the initial state, you include the name of the property or properties you want to affect in the transition, along with the time of the transition, the speed, and the delay, if any. Here is the syntax as shown in the spec:[68]

nav a {
    background-color: rgb(255,255,255);
    border: 5px solid #000000;
    transition-property: background-color;
    transition-timing-function: linear;
    transition-duration: 0.8s;
    transition-delay: 200ms;
}

This is the initial keyframe. A keyframe is a drawing that defines the starting or ending points of any smooth transition in animation (or in film). Keyframes aren’t explicitly employed in transitions. They are used when creating animations using the animation properties, which we discuss later in this chapter. Getting an understanding now may help make animations less confusing later.

You then define the value of the properties listed in the value of transition-property: in this case, the value of the background-color. We could have used the key term all if we had wanted to transition all the transitionable properties that change between the default and transitioned state, such as the hover state:

nav a:hover, nav a.hover {
    background-color: rgb(0, 0, 0);
    border: 5px dashed #CCCCCC;
}

In the preceding example, when the user hovers over a link in the navigation section of the document, the background of the link will go from white to black, as shown in Figure 10-1. The border color and style will change immediately on hover. The transition of the background color will start after 200 milliseconds; take 800 milliseconds to transition, and transition at an even keel. In Figure 10-1, you’ll note the border changed immediately, but the background color waited the 200 ms delay before starting to transition.

This may be a lot to grasp, so let’s go over the various transition values. There are no screenshots in this section, since “effects” are hard to perceive in print. However, there are examples in the online chapter resources.

The transition-property Property

The transition-property lists the properties that will be transitioning during the animation. Properties that can be made to transition include:

For example, top: 0 to top: 100px; has a midpoint of 50 px and is therefore transitionable, but display: block to display: none; (used as example property values in Table 10-1) does not have a midpoint and is not transitionable. You can transition from height: 600px to height: 700px; but not from height: auto; to height: 700px;.

While you can’t currently transition background images, including gradients, you can transition background-position and background-size to create some interesting effects.

The exception to this midpoint rule is visibility. Visibility is a property that seemingly you wouldn’t be able to transition, but you can include it in transitions and animations. The value will actually jump from visible to hidden at the end of the transition effect if those are the property values set. There is discussion of making all properties transitionable and animatable, but we aren’t there yet.

The value of the transition-property property is a comma-separated list of any number of these property names, or the keyword all. Only the properties listed as the value of the transition-property will transition over time when the transition gets initiated, unless all is declared:

nav a {
  -webkit-transition-property: background-color; /* iOS6-, BB, Android, Ch25-*/
  -moz-transition-property: background-color; /* FF4 to 15 */
  -o-transition-property: background-color; /* O 10.5 to 12 */
  transition-property: background-color; /* IE10, FF16+, 012.5+, Ch26+, iOS7 */

I’ve included -moz- and -o-, to demonstrate support for older browsers. -ms- has never been needed, and Firefox and Opera are not needed in currently used mobile browsers. The only prefix we need to include for the transition properties in the mobile space, or at all, is -webkit-.

If a property that is not transitionable is included in the value of the transition-property property, the value will be ignored, changing state instantly instead of transitioning over time. The value that can’t transition over time will not, but the transition itself will not fail.

The transition-timing-function enables control over the transition, describing how the animation will proceed over time. The value can take one of several keywords—ease, linear, ease-in, ease-out, ease-in-out, step-start, step-end, steps(x, start), steps(x, end)—or take as its value a cubic Bézier function.

A cubic Bézier takes as its value four points in a plane: starting at the first point going toward the second, and arriving at the last point from the direction of the third point. After about three years of calculus, it might make sense! Fortunately, some cubic Bézier curve values are included in CSS3 as predefined keywords, and links to tools to help you better understand cubic Bézier are included in the online chapter resources.

The nonstep keyword values (ease, linear, ease-in-out, etc.) are each themselves representing cubic Bézier curve with four fixed-point values:

ease, or cubic-bezier(0.25, 0.1, 0.25, 1.0)

The default value; increases in velocity toward the middle of the transition, slowing back down at the end.

linear or cubic-bezier(0.0, 0.0, 1.0, 1.0)

Transitions at an even speed.

ease-in or cubic-bezier(0.42, 0, 1.0, 1.0)

Starts off slowly, with the transition speed increasing until complete.

ease-out or cubic-bezier(0, 0, 0.58, 1.0)

Starts transitioning quickly, slowing down as the transition continues.

ease-in-out or cubic-bezier(0.42, 0, 0.58, 1.0)

Starts transitioning slowly, speeds up, and then slows down again.

cubic-bezier(p1,p2,p3,p4)

Where the p1 and p3 values must be in the range of 0 to 1.[69]

The step functions—steps(x, end), steps(x, start), step-end, and step-start—divide the duration of the transition into equal lengths of time. Each interval is an equal step in terms of time taking the transition from original state to final state. The function also specifies whether the step is at the start or end of the interval.

In other words, if there are five steps, steps(5, start) will have steps representing 0%, 20%, 40%, 60%, and 80% of the progress toward the final state. If you set steps(5, end), you will have steps representing 20%, 40%, 60%, 80%, and 100% of the progress.

The value step-start is equivalent to steps(1, start) and step-end is equivalent to steps(1, end). The steps() values are good for animating background image sprites to create animations. There are some examples of these values in the online chapter resources. Continuing on with our sample code:

    ...
-webkit-transition-timing-function: linear;
        transition-timing-function: linear;
    ...

The transition-delay property specifies the number of milliseconds or seconds to wait between a change of state causing the transition and the start of the transition effect. The default value of 0s indicates that the animation should begin to transition immediately. Positive time values will delay the start of the transition effect for the value indicated. Negative values cause the transition to start right away, but start midway through the transition.

While transition-delay may seem like a useless property, it can greatly help improve user experience. Oftentimes you don’t want hover or touch effects to be too sensitive. If a user is dragging a finger or mouse quickly across the screen to get from point A to point B, you don’t want all the points in between that are accidentally touched to react to the touch too quickly. A transition delay of 50 ms generally does the trick. The intentional touches still seem reactive, but the page doesn’t flicker as object transitions get unintentionally activated.

Negative transition delays can also serve a purpose. As long as the absolute value of the delay is less than the transition-duration, the transition, when initiated, will start midway through the transition at a point proportional to the time difference. For example, if you have a 10 second transition-duration, and a −4 second delay, the transition will start immediately, but at 40% through the transition:

...
-webkit-transition-delay: 250ms
        transition-delay: 0.25s;
}

But what if you want to transition more than one property? Maybe you want to change background-color, border-color, and color all on the same property? The transition properties allow for multiple transitions in one call.

Let’s say instead of just transitioning the background-color property, we want to transition the border property as well (you can transition the border color and width but not style). We would have to (1) include the new border property in the transitioned style declaration, and (2) include the border property in the transition-property value list, either as a comma-separated series of values, or use the key term all. Note that all should only be used if you want to transition all properties in the same way.

For instance, in earlier examples, although we defined the border and background-color properties in both the touch or hover states, we only included the background-color property as the value of the transition-property property. In those examples, the background-color will transition slowly (over 250 ms), and the border color will change immediately on hover or touch: like you are used to, like it has always done without transition, as if the transition were set to a 0 s duration with a 0 s delay.

If you want to change both properties at the same rate and delay, you could write your transition shorthand using the all keyword, since you are transitioning all the properties listed in the hover status:

nav a {
  background-color: #FFFFFF;
  border: 5px solid #CCCCCC;
  -webkit-transition: all 500ms linear 250ms;
          transition: all 500ms linear 250ms;
}

When using the all keyword, all the properties transition at the same rate, speed, and delay. If you want some but not all of your properties to transition at the same rate, timing, and delay, comma separate the transition-property properties:

nav a {
  background-color: #FFFFFF;
  border: 5px solid #CCCCCC;
  color: red;
  -webkit-transition: border, color 500ms linear 250ms;
          transition: border, color 500ms linear 250ms;
}

If you don’t want all your properties to transition at the same rate, want some to have a greater delay than others, or if you just want a few properties to have a transition effect, include the various transition properties as a comma-separated list, including, at minimum, the transition-property and transition-duration for each:

 nav a {
  background-color: #FFFFFF;
  border: 5px solid #CCCCCC;
  color: red;
  -webkit-transition:
    background-color, color 500ms linear 750ms,
    border 500ms linear 250ms;
  transition:
    background-color, color 500ms linear 750ms,
    border 500ms linear 250ms;
}

In this example, the border, which has the shortest transition-delay, will transition first. When the border has finished transitioning, at the 750 ms point—which is the transition-delay property, and the value of the 500 ms for the border transition plus the 250 ms delay—the background-color and color will both then transition over half a second:

transition-property:    background-color, color, border;
transition-duration:    500ms;
transition-timing-function: linear;
transition-delay:       750ms, 750ms, 250ms;

We could also have used the longhand properties, with each property comma separated, but that would have been more code in this scenario, as shown previously. I find the shorthand syntax easier to write, generally shorter to write, easier to understand, and easier to maintain.

In CubeeDoo, we transition our card flips over 0.25 s. We haven’t learned how to flip a card yet (it’s covered in the next section), but we can tell it to flip everything, immediately over 0.25 s with:

1     #board > div {
2         position: relative;
3         width: 23%;
4         height: 23%;
5         margin: 1%;
6         float: left;
7         -webkit-transition: 0.25s;
8         transition: 0.25s;    ...

In the default, pre-flipped state, in line 7 and 8, we tell the cards that when the state is changed, transition all (default for transition-property) the properties immediately (default for transition-delay) in the default transition-timing-function of ease.

I wrote transition: 0.25s, but I could have also written transition: all 0.25s ease 0ms;, which may be longer, but might be easier to maintain as author intentions are clearer with the latter.

Transforms allow you to resize, rotate, skew, translate, and otherwise reposition elements. There is a 2D version of the transforms module that is supported by all browsers, starting in IE9,[71] and another for 3D, with support spreading quickly.

Note

Unlike transitions, transforms are supported in IE9.

CSS3 transforms allow for various transformations to be applied to an element, including multiple transforms on a single element.

Two CSS properties are used to create a transform: the transform property specifies the types of transformations you want to apply to the element, and the transform-origin property sets the point of origin from where the transform takes place.

The first step we’re covering is setting the origin of the transform. The transform-origin property establishes the origin of transformation for an element.

The default transform-origin value is 50% 50% 0, which is the center of the element. The first value specified is the x coordinate, or left/right value, and the second value is the y coordinate, or the top/bottom value, with the values being calculated from the top left corner of the element. The third value is the z-offset, which is relevant when doing 3D transforms. The values can be specified using a length, a percentage, or the keywords left, center, right, top, center, and bottom, with the optional z-offset being a length only that is not a percentage.

The point set by the transform-origin is the point around which the transform will occur. As Figure 10-2 demonstrates, when the point of origin is set to the center point of an element (the default), the element will transform, in this case rotate, around that center point. When the transform-origin is set to a different location, such as the top left as shown in Figure 10-2, the element transforming, such as rotating, around the origin point in the top left will create a different effect. The element orbits around this point.

The transform-origin on the left of Figure 10-2 is the default, so can be omitted. The syntax for the effect on the right can be written as:

-webkit-transform-origin: top left 0; /* all webkit & blink browsers */
   -moz-transform-origin: top left; /* FF 3.5 - 15 */
    -ms-transform-origin: 0 0; /* IE9 */
     -o-transform-origin: 0 0 0; /* O 11.0-12.0 */
        transform-origin: top 0 0; /* IE10+, FF16+, O12.1 only */

... where top left, 0 0, 0 0 0, and top left 0 are all equivalent.[72] Once the point of origin is set (or omitted, so set to the default value of transform-origin: center center 0;), the type of transform is applied. This is set with the transform property with a list of one or more transforms as the value.

The transform Property

Supported in Firefox 3.5+, Opera 10.5, Internet Explorer 9, and WebKit since before the iPhone even came out, the CSS transform property lets you modify the coordinate space of the CSS visual formatting model. Using it, elements can be translated, rotated, scaled, and skewed. CSS transforms modify the coordinate space, allowing us to change the position of the affected content without disrupting the normal flow. The location and amount of space a transformed element takes is the location and space used by the element before transforms were applied.

We manipulate an element’s appearance using transform functions. The value of the transform property is a list of space-separated transform functions applied in the order provided. The transform functions include:

The skew(x,y) function, as shown in Figure 10-6, specifies a skew along the x- and y-axis. The x specifies the skew on the x-axis, the y specifies the skew on the y-axis. If there is only one parameter, then it’s the same as skew(x, 0deg), or skewX(x). The values are angles, degrees, turns or grads:

-webkit-transform: skew(15deg, 4deg);
    -ms-transform: skew(15deg, 4deg);
        transform: skew(15deg, 4deg);

The previous section showed single transforms, but you can include more than one transform on an element. To include more than one transform, simply separate the transform functions with spaces:

.enlargen:hover, .enlargen.hover {
 -webkit-transform: translate(−50%, −50%) scale(2) rotate(0deg);
     -ms-transform: translate(−50%, −50%) scale(2) rotate(0deg);
         transform: translate(−50%, −50%) scale(2) rotate(0deg);
}

This makes the element twice as tall and twice as wide. By translating the element 50% up and to the left, the bottom-right corner should remain in the exact same location. Declaring rotate(0deg) is unnecessary, since any transforms declared with a selector with weaker specificity that included a rotate function would be overwritten, regardless of whether we included the rotate function. I’ve included this as a reference for how to include the rotate() transform function, and to remind you to include the unit whenever you are using non-length units.

Warning

Even though the rotation value is zero degrees, you must include the unit for degrees, just as you must with time (s or ms), rads, grads, turns, Hz, and kHzs.

Note that the transition-property property values are comma separated, and the transform functions are space separated.

This enlargen class may be something you would want to add to an image gallery, highlighting an image that is hovered by making it four times larger (twice as wide and twice as tall) and remove any tilt that might have been interesting as a thumbnail, but tacky in full size.

Browsers have been a bit slower in supporting 3D transforms, but they’re getting there. 3D transforms have been supported since iOS 3.2, Android 3, Blackberry 10, Firefox 10, IE10, Safari 4, and Chrome 12, all with vendor prefixes. 3D transforms are slated to be supported in Opera 15 with the Blink rendering engine. 3D transforms have only been supported since iPhone 2 (not the original), and is only supported if you have Mac OS X v10.6 or newer.

CSS 3D transforms enable positioning elements on the page in three-dimensional space. Just like before, you can combine 3D transforms with transitions (and animations described later) to create 3D motion.

Similar to the 2D transform functions, most browsers support 3D transform properties, starting with IE10. As of this writing, the -webkit- prefix is required for WebKit and Blink browsers.

A few things to note about 3D transforms is that elements that are transformed into a 3D space (1) are hardware-accelerated, and (2) have their own stacking context.

Earlier we were introduced to some transform properties. To successfully implement 3D transforms, we are provided with some new properties, and enhancements to some properties introduced for 2D transforms.

We use a lot of the features just listed in our card flipping in CubeeDoo:

#board > div {
     position: relative;
     width: 23%;
     height: 23%;
     margin: 1%;
     float: left;
     -webkit-transition: 0.25s;
     transition: 0.25s;
     -webkit-transform: rotatey(0deg);
     transform: rotatey(0deg);
     -webkit-transform-style: preserve-3d;
     transform-style: preserve-3d;
     box-shadow: 1px 1px 1px rgba(0,0,0,0.25);
     cursor: pointer; /* for desktop */
}
#board.level2 > div {
     height: 19%;
}
#board.level3 > div {
     height: 15%;
}
.back,
.face,
.back:after,
.face:after {
     position: absolute;
     content: "";
     top: 0;
     left: 0;
     right: 0;
     bottom: 0;
     border-radius: 3px;
     pointer-events: none;
     -webkit-backface-visibility: hidden;
     backface-visibility: hidden;
}
.back {
     border: 5px solid white;
}

.back:after {
     font-size: 2.5rem;
     line-height: 100%;
     background:
          50% 50% no-repeat,
          0 0 no-repeat #fff;
     font-style: normal;
     box-shadow: inset 1px 1px 0 currentcolor,
          inset −1px −1px 0 currentcolor,
          1px 1px 1px rgba(0,0,0,0.1);
     color: rgb(119, 160, 215);
     background-image:
          url('data:image/svg+xml;utf8,<svg width="40" height="40" 
            xmlns="http://www.w3.org/2000/svg"><g><text xml:space="preserve" 
            text-anchor="middle" font-family="serif" font-size="40" id="svg_1" 
            y="30" x="20" stroke-width="0" stroke="rgb(119, 160, 215)" 
            fill="rgb(119, 160, 215)">❀</text></g></svg>'),
          -webkit-linear-gradient(-15deg, 
            rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.025));
     background-image:
          url('data:image/svg+xml;utf8,<svg width="40" height="40" 
            xmlns="http://www.w3.org/2000/svg"><g><text xml:space="preserve" 
            text-anchor="middle" font-family="serif" font-size="40" id="svg_1" 
            y="30" x="20" stroke-width="0" stroke="rgb(119, 160, 215)" 
            fill="rgb(119, 160, 215)">❀</text></g></svg>'),
          linear-gradient(75deg, 
            rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.025));
     -webkit-transform: rotatey(0deg);
     -webkit-transform: rotatey(0deg) translatez(0);
     transform: rotatey(0deg)
     transform: rotatey(0deg) translatez(0);
}
.face {
     -webkit-transform: rotatey(180deg);
     -ms-transform: rotatey(180deg);
     transform: rotatey(180deg);
}
#board > div.flipped {
     -webkit-transform: rotatey(180deg);
     -webkit-transform: rotatey(180deg) translatez(0);
     transform: rotatey(180deg);
     transform: rotatey(180deg) translatez(0);
     box-shadow: −1px 1px 1px rgba(0,0,0,0.25);
}

In CubeeDoo, we use transforms to flip the card, and transitions to do the flip in 250 milliseconds. The cards are shells with two children: the face and back of the card. Because we are using CSS classes to style the front of our cards, we can create all our card faces with generated content. It is easier to maintain a single code path. So, although we could have put the colors’ color scheme directly on the face with background-color, and put the SVG shapes as a background-image directly on the face <div>, the number and second shapes theme need to be generated content. To simplify, we put the SVG and colors on the ::after pseudoelement generated content as well.

When the user taps or clicks on a card, the card flips. This is done with transform: rotatey(180deg) translatez(0); on the card container. The issue is that the HTML always has the back after the face in the source order:

<div data-value="0" data-position="2">
    <div class="face"></div>
    <div class="back"></div>
</div>

Therefore, the back will always sit on top of the face. To hide the back when the card is flipped and show the face instead, we add backface-visibility: hidden;. That way, when the card is facing away from us, we do not see the elements that are facing away from us (we’ll see the face and not the back).

By adding the 3D transform of translateZ(0), we hardware accelerate it in supportive devices, ensuring that the animation will be performed on the GPU instead of the CPU. The reason we include four declarations:

    -webkit-transform: rotatey(180deg);
    -webkit-transform: rotatey(180deg) translatez(0);
            transform: rotatey(180deg);
            transform: rotatey(180deg) translatez(0);

... is because not all browsers support 3D transforms. If a browser doesn’t understand a line of CSS, it skips the whole property/value declaration. Therefore, we first declare without translateZ() for browsers that don’t understand it, then with translateZ() for browsers that do, both prefixed and unprefixed. That third line—unprefixed yet targeting browsers not supporting 3D—will be understood by browsers that support transforms but not 3D transforms.

We also include transform-style: preserve-3d, as we want to ensure the front and the back of the card—the card’s children—are in the same 3D space as the card.

As we move from game level 1 to level 2 to level 3, the height of the cards gets smaller. Since our transition declaration of 0.25 s is on the default state, when the cards shrink, they do so over 0.25 s.

Generally, you don’t want to transition height. Transitioning box model properties causes the browser to reflow every frame, causing unnecessary reflows and repaints. This is even more of an issue on mobile, and is exacerbated by having a large number of DOM nodes. A possible solution would be to transition a transform: scaleY(0.8), as that would maintain the cards’ width but only shrink the height, but this will distort the shapes and number themes. We’ll leave this as is. The card height changes only happen a maximum of two times per game. We were careful not to have too many DOM nodes. This transition, with the recalculation of all of the DOM nodes, while not optimal, works well enough for this scenario.

A better solution would be to animate the scaling and switch out the class based on the animation end. As you’ll see next, animation is much more powerful than transitions. With animations, we would be able to change a class or otherwise add event listeners at the animation’s end.

As a counterpart to transitions and transforms, explicit animations provide a way to declare repeating animated effects with keyframes.

For simple transitions, when the starting value and ending value are known, and only a single iteration of the animation is required, the transition properties may suffice for your animating needs. If you need finer control of the intermediate values of your animation, or you need to repeat your animation, the animation properties of the CSS3 animation module, with keyframes, can be used.

The animation properties include:

animation-name

The name you gave your keyframe animation definition, or a comma-separated list of multiple animation names. The default value is none, or no animation. Obviously, therefore, you need to include an animation-name if you want an element to be animated.

animation-duration

The length of time in seconds or milliseconds an animation takes to complete one cycle. The default value is 0s, which means that no visible animation will take place. In other words, the animation-duration property, with a value of greater than 0 seconds, is required.

animation-timing-function

How the animation will progress over one cycle of its duration, taking the same values as the transition-timing-function. Although the values are discreet, you can “animate” the animation-timing-function in your keyframe definitions. The default value is ease.

animation-iteration-count

Number of times an animation cycle is played as an integer, or infinite. The default value is a single iteration.

animation-direction

Whether or not the animation should play in reverse on alternate cycles (alternate) or not (normal).

animation-play-state

Defines whether the animation is running or paused. A paused animation displays the current value of the animation in a static state. When a paused animation is resumed, it restarts from the current value. The default value is running.

animation-delay

Defines when the animation will start. Interestingly, if the value is negative, the animation will start partway through the animation. For example, in a 10 second animation, if the animation-delay is -4s, the animation will start immediately 40% of the way through the first animation cycle.

animation-fill-mode

Defines what values are applied by the animation before the animation starts and after it ends. There are 4 possible values:

animation

The shorthand for the animation properties, space-separated values for the animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, and animation-fill-mode properties. For a multiple animation declaration, include a grouping for each animation name, with each shorthand grouping separated by a comma.

Keyframe animations involve setting the state of your elements at different stages of an animation. Keyframes are specified using the @keyframes rule. The rule consists of the keyword @keyframes, followed by an identifier giving a name for the animation followed by a set of style rules encased in curly braces ({}). You create the identifier[73] (name) of your animation. Do not quote the animation name or identifier. This name is the name used as the value of the animation-name property.

The keyframe selector for a keyframe style rule consists of a comma-separated list of values. You can use percentage values or the keywords from and to. For example:

@keyframes crazyText {
     from {
          font-size: 1em;
     }
     to {
          font-size: 2em;
     }
 }

In WebKit browsers, this would read as follows:[74]

@-webkit-keyframes crazyText {
     from {
          font-size: 1em;
     }
     to {
          font-size: 2em;
     }
 }

The keyword from is equivalent to the value 0%. The keyword to is equivalent to the value 100%. Note that the percentage unit specifier must be used. Therefore, “0” is an invalid keyframe selector.

If you need to define more than two points, more than the start and finish of an animation, having more granular control of the animation by defining keyframes for points in between, use percentages. For example:

@-prefix-keyframes rainbow {
 0% {
    background-color: red;
 }
 20% {
    background-color: orange;
 }
 40% {
    background-color: yellow;
 }
 60% {
    background-color: green;
 }
 80% {
    background-color: blue;
 }
 100% {
    background-color: purple;
 }
}

The keyframe selectors are used to specify the percentage along the duration of the animation that the keyframe represents. An analogy would be that the 20%, 40%, 60%, and so on are pseudoclasses of the transition duration (they’re not, but you can think of them that way). The keyframe is specified by the style rules (the code block of property values) declared on the keyframe selector.

The percentage or keyframe selector determines the placement of the keyframe in the animation. To make them easy to follow, I recommend keeping the keyframe selectors in order of the progression, from 0% to 100%, though this is not required.

Style blocks for keyframe selectors consist of properties and values. The animatable properties are listed in CSS3 Transforms. Properties that are unable to be animated are ignored in these rules.

To determine the set of keyframes, all of the values in selectors are sorted in increasing order by time. Keyframe selectors do not cascade; therefore an animation will never derive keyframes from more than one keyframe selector. If there are any duplicates, then the last keyframe selector specified inside the @keyframes rule will be used to provide the keyframe information for that time.

The animation engine will smoothly interpolate style between the keyframe selectors. In these examples, shown in the online chapter resources with an animation duration of 10 seconds with linear animation timing function: at the 5 second mark, the crazyText is at font-size: 1.5em and the element with the “rainbow” animation has a yellowish-green background.

We may have defined an animation, but we haven’t attached an animation to any elements. Once we have defined an animation with @keyframes, we apply it using, at minimum, the two required properties: animation-name and animation-duration. The other related animation properties are optional:

div {
  animation-name: crazyText;
  animation-duration: 1s;
  animation-iteration-count: 20;
  animation-direction: alternate;
  animation-delay: 5s;
  animation-fill-mode: both;
}

The preceding rule attaches the “crazyText” animation, sets the duration to 1 second per iteration, makes it execute a total of 20 times with every other iteration play in reverse, waiting 5 seconds before commencing the first iteration.

The animation-fill-mode of both means that when this <div> is first rendered to the page, the font-size will be set to 1em as per the 0% or from keyframe, prior to the 5 second delay before the first iteration starts.

The font-size will then double in size over one second, then shrink back to 1em over the next second because we set animation-direction: alternate. Had we set animation-direction: normal, or omitted the animation-direction property altogether, the font-size would have doubled in size over one second, and then jumped back to 1em before doubling over one second again in the second iteration and all subsequent iterations.

When the animation has completed the 20 iterations, 25 seconds after the animation was applied (20 one-second iterations plus a five-second delay), the font-size will remain at the last keyframe because we set animation-fill-mode: both. With animation-direction: alternate, the font-size will be 1em. Had we omitted the animation-direction property altogether or set animation-direction: normal, then the last keyframe would have been at 2 em. The <div> will remain at this font size “forever” unless a font-size declaration targeting this node overrides it.

This could have been written, with some padding and the rainbow animation added, with the shorthand:

div {
 padding: 20px;
 animation:
    crazyText 1s 20 5s alternate both,
    rainbow 4s infinite alternate;
}

As with transitions, any property that has a discoverable midpoint can be animated. Two exceptions are visibilility and animation-timing-function. Neither has a midpoint, but both can be added to a keyframe style block.

If you include the animation-timing-function in a keyframe, the animation will switch from the default or current timing function to the newly declared timing function at that point. Rarely used, this can actually come in handy. For example, if you are creating a bouncing ball, gravity dictates that the ball will get progressively faster (or ease-in) as it drops, and progressively slower as it bounces up (or ease-out):

@keyframes bouncing {
  0% {
   bottom: 200px;
   left: 0;
   animation-timing-function: ease-in;
  }
  40%, 70%, 90%{
   animation-timing-function: ease-out;
   bottom: 0;
  }
  55% {
   bottom: 50px;
   animation-timing-function: ease-in;
  }
  80% {
   bottom: 25px;
   animation-timing-function: ease-in;
  }
  95% {
   bottom: 10px;
   animation-timing-function: ease-in;
  }
  100% {
   left: 110px;
   bottom: 0;
   animation-timing-function: ease-out;
  }
}

There are several things to note about this code example (which is in the online chapter resources). We have three keyframes that have the exact same values—the 40%, 70%, and 90% keyframes—so we put them all on one line. We separated the keyframe selectors with commas just as we do normal CSS selectors.

We have more than one property being animated, but we don’t declare all the values in every keyframe block. The left-to-right motion is smooth, and therefore we only needed to declare that property twice: in both the 0% and 100% blocks. We did move the ball up and down numerous times with granular control, so unlike the left value, we declared the bottom value in every keyframe.

We’ve also animated or changed the animation-timing-function to make the bounce look smooth and natural. Without it, our bouncing ball animation was very jumpy. The animation-timing-function is the only animation property that can be included within keyframe declarations.[75]

Animating sprites

Generally when I think of HTML and animation, I think of animating a single node to a new position, or something dreadfully boring. One feature of CSS animations is creating character animations, like animating lemmings as they jump, or in this case float, off a cliff.

To create character animation using a sprite, we use the animation-timing-function: step(x, start) value. The step values don’t move smoothly through the keyframes. Rather, they break up the animation into the number of steps declared and jump from one step to the next. To animate the sprite in Figure 10-7, we move the background image:

.lemming {
    height: 32px;
    width: 32px;
    background-image:url(lemming.gif);
    background-repeat: no-repeat;
    -webkit-animation: lemming 1s steps(8,end) alternate infinite;
    animation: lemming 1s steps(8,end) alternate infinite;
}
@-webkit-keyframes lemming {
  from {
   background-position: 0 0;
 }
 to {
   background-position: −256px 0;
 }
}

@keyframes lemming {
  from {
   background-position: 0 0;
 }
 to {
   background-position: −256px 0;
 }
}

Note that the sprite is 256 px wide, so our background-position: 256px 0; would normally show no background image. With animation-timing-function, you can declare steps(x, start), which specifies that the change in the property value happens at the start of each step and steps(x, end), which means the change in property comes at the end of the step.

Let’s use an animation with five steps. If you declare steps(5, start) the jump will be at the start of the step, so you’ll get five steps at the 20%, 40%, 60%, 80%, and 100% marks—or basically the 0% really doesn’t show, because the change in property from 0 to 20% happens at the start of the step, the viewer sees the 20% mark from 0 to 20% time. If you declare steps(5, end), the jump to the next step will be at the end of the interval, so it will appear to show the 0%, 20%, 40%, 60%, and 80% marks. This is why our last “step” is beyond the width of the sprite since the 100% mark is never shown.

By creating a motion sprite and animating the background-position, moving that sprite using steps, you can create motion animations. The online chapter resources have more examples of sprite animation.

In CubeeDoo, we have very simple animations. When a pair of cards is matched, the cards are animated as they disappear. And, when a score is a high score, it is highlighted with a blink-like animation in the high score area on larger screens:

#board > div.matched {
     -webkit-animation: fade 250ms both;
     animation: fade 250ms both;
}
#board > div.matched:nth-of-type(1) {
     -webkit-animation-delay: 250ms;
     animation-delay: 250ms;
}

@-webkit-keyframes fade {
     0%  {
        -webkit-transform: scale(1.0) rotatey(180deg) rotate(0) translatez(0); 
     }
     100% {
         -webkit-transform: scale(0) rotatey(180deg) rotate(720deg) translatez(0); 
     }
}
@keyframes fade {
     0%  {
       transform: scale(1.0) rotatey(180deg) rotate(0) translatez(0); 
     }
     100% {
       transform: scale(0) rotatey(180deg) rotate(720deg) translatez(0);
     }
}

#highscores li.current {
     -webkit-animation:
          winner 500ms linear 8 alternate forwards;
     animation:
          winner 500ms linear 8 alternate forwards;
}
@-webkit-keyframes winner {
     0% {background-color: hsla(74, 64%, 59%,1);}
     100%{background-color: hsla(74, 64%, 59%,0)}
}
@keyframes winner {
     0% {background-color: hsla(74, 64%, 59%,1);}
     100%{background-color: hsla(74, 64%, 59%,0)}
}

When the matched class is added to a card, the fade animation gets attached to that card. The matched class is added to the two cards that have the flipped class if the two flipped cards match. Otherwise, if the two flipped cards aren’t a match, the flipped class is removed, and the transition described in our sections on transitions occurs. We’ve included 3D transforms to ensure that the animation is handled on the GPU instead of the CPU if possible in the browser and device.

The fade animation is a misnomer. It doesn’t fade. It spins and shrinks over 250 ms. At the end of the animation, the JavaScript removes both the flipped and matched classes, and resets and hides the card by setting data-value="0" attribute/value pair.

When the game ends, the list of high scores is regenerated. If the current high score is one of the top 5 high scores, the class current gets added to the score as it gets written to the page. We control adding and removing class names with JavaScript, and we create and execute animations with CSS purely based on the class. In the case of a high score, the winner animation is executed, which is a fading green background color that pulses on and off eight times: four times from fully opaque to fully transparent, and four times from fully transparent to fully opaque. This is controlled by declaring animation-iterations: 8; with animation-direction: alternate; in the shorthand animation property. The current high score will remain with a green background until the scores are redrawn. We didn’t put this animation in a 3D space as we are doing a very simple repaint with no reflow, so it should perform well by default.

CSS animations allow you to write declarative rules for animations, replacing lots of hard-to-maintain animation code in JavaScript.

Browsers are optimized to handle CSS animations. As such, CSS animations are more performant than JavaScript animations. If you can, always use CSS to animate instead of JavaScript. By using CSS, you allow the browser to optimize tweening and frame skipping, letting the browser optimize for performance.

While CSS animations are more performant than JavaScript animations, there are some drawbacks. Similar to JavaScript animations, CSS animations do suck up battery power. But CSS doesn’t occupy the CPU like JavaScript, so will generally be less jumpy.

CSS has last priority on the UI thread. This means that if you are downloading a huge JavaScript file that takes eight seconds to load, the page will not start animating during those eight seconds. While this may not seem like a big issue, there is a quirk: while the animation won’t start, the animation-delay expires. So, if you have 15 animations each starting a second apart using animation delay of 0 s, 1 s, 2 s, and so on, the first eight animations will all happen when the page had finally finished loading and rendering, and the next seven animations will each occur when they were timed to occur.

To resolve this issue, you can add a loaded class to the document and base the animations on being an element that is a descendant of the loaded class.

Also, some properties perform better than others when animated. If you change the layout of the page forcing a reflow, the animation will not perform as well as when your animation is simply a repaint of an object. For example, the increase font size animation we did earlier is a stupid animation! The animation forces repeated reflows of the page: as font resizes, the element is resized. As the element is resized, the entire document is reflowed before the repaint. If we simply scaled the element using CSS transforms, and animated the transform, the browser would only be redrawing the animated element, which performs much better.

Remember that reflows are expensive and take more rendering time. To appear smooth, you want your animation frames to be fully drawn in under 16.67 ms.

There is also an animations API to capture events from the CSS animations. animationStart, animationEnd, and animationIteration are events that occur with every iteration of the animation. We aren’t detailing this here, but there are links to resources in the online chapter resources.

We’re not done with CSS: we still have a few more features to discuss, which we do in Chapter 11.



[67] It is possible to drain battery animating certain CSS property values, but generally, browsers are well optimized for CSS.

[68] Vendor prefixing is needed: -webkit- up through iOS6, Android, BlackBerry 10, and Chrome through 25; -o- for Opera up to 12; and -moz- for Firefox up to 15. Support began in IE with IE10 unprefixed. I recommend still including -webkit-, but the other prefixes are optional as browsers requiring them are already almost obsolete.

[69] While explaining cubic Bézier is beyond the scope of this book, there is a good tool with which you can determine what other values you might need for your timing function. Another site, http://cubic-bezier.com, lets you compare the forward progress of one timing function against another.

[70] Depends on the number of vendor prefixes you include.

[71] Earlier versions of Internet Explorer support transitions via filter: progid:DXImageTransform.Microsoft.Matrix().

[72] Vendor prefixing is required for IE9, Firefox 3.5 to 15, Opera through 12, and all versions of WebKit browsers. It is no longer required beginning with Firefox 16, IE10, Opera 12.1, and Opera Mobile 11. Opera Mini does not support transforms. All browsers that support transform-origin support all of the 2D transform functions. Opera was prefixless in version 12.1, but the -webkit- prefix became required when they changed the browser engine away from Presto.

[73] In CSS, IDENTs, or identifiers, including element names, classes, IDs, and keyframe animation names, and can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit. Identifiers are not quoted.

[74] Animations are supported in IE10, BlackBerry, Android, Chrome for Mobile, iOS, and all modern mobile browsers. They are not supported in Opera Mini, which is expected. They are still prefixed only in WebKit browsers and Boot2Gecko. Firefox dropped the prefix in Firefox 16, Opera with Opera 12.1, with Opera Mobile never having support for the prefixed version. IE began supporting animations with IE10, sans prefix.

[75] To learn more about how to code animations, the online chapter resources have links to an animation tutorial deck that shows every animation property “in action.”