Transitions - CSS: The Definitive Guide (2017)

CSS: The Definitive Guide (2017)

Chapter 10. Transitions

CSS transitions allow us to animate CSS properties from an original value to a new value over time when a property value changes.

Normally, when a CSS property value changes—when a “style change event” occurs—the change is instantaneous. The new property value replaces the old property in the milliseconds it takes to repaint (or reflow and repaint if necessary) the affected content. Most value changes seem instantaneous, taking less than 16 ms1 to render. Even if the changes takes longer, it is a single step from one value to the next. For example, when changing a background color on hover, the background changes from one color to the next, with no gradual transition. CSS transitions enable us to smoothly animate CSS properties from an original value to a new value over time as the style recomputation proceeds:

button {

color: magenta;

transition: color 200ms ease-in 50ms;

}

button:hover {

color: rebeccapurple;

transition: color 200ms ease-out 50ms;

}

Transitions allow the values of CSS animatable properties to change over time, providing for simple animations.2 For example, instead of instantaneously changing a button’s color on hover, with CSS transitions the button can be set to gradually fade from magenta to rebeccapurple over 200 milliseconds, even adding a 50-millisecond delay before transitioning. Changing a color, no matter how long it takes, is a transition. But by adding the CSS transitionproperty, the color change can be gradual.

You can use CSS transitions today, even if you still support IE9 or older browsers. If a browser doesn’t support CSS transition properties, the change will be immediate instead of gradual, which is fine and accessible. If the property or property values specified aren’t animatable, again, the change will be immediate instead of gradual.

Because transitions are simply progressive enhancements, there is no reason to not use them today.

CSS Transitions

CSS transitions provide a way to control how a property changes from one value to the next over time. We can make the property value change gradually, creating pleasant and, hopefully, unobtrusive effects.

The CSS transition properties can be used to animate CSS property values smoothly, following an acceleration curve, after an optional delay, from a previous value to a new value over a specified length of time. CSS transitions let you decide which properties to animate, how long to wait before the animation starts, how long the transition should take, and how the transition will proceed. All these features are customizable.

Sometimes you want instantaneous value changes. Though we used link colors as an example in the preceding section, link colors should change instantly on hover, informing sighted users an interaction is occurring and that the hovered content is a link. Similarly, options in an autocomplete listbox shouldn’t fade in: you want the options to appear instantly, rather than fade in more slowly than the user types. Instantaneous value changes are often the best user experience.

At other times, you might want to make a property value change more gradually, bringing attention to what is occurring.

For example, you may want to make a card game more realistic by taking 200 milliseconds to animate the flipping of a card https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round3, as the user may not realize what happened if there is no animation.

As another example, you may want your site’s drop-down menus to expand or become visible over 200 milliseconds (instead of instantly) which may be jarring. With CSS transitions, you can make a drop-down menu appear slowly. In Figure 10-1, we are transitioning the submenu’s height and opacity over 200 milliseconds. https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round The menu changes from hidden to fully opaque and expanded. This is a common use for CSS transitions, which we will also explore later in this chapter.

taic 0201

Figure 10-1. Transition initial, midtransition, and final state

In this chapter, we cover the four transition properties and the transition shorthand that not only make our transition possible but very easy to implement.

Fallbacks: Transitions Are Enhancements

Transitions have excellent browser support. All browsers, including Safari, Chrome, Opera, Firefox, Edge, and Internet Explorer (starting with IE10) support CSS transitions.

Transitions are user-interface (UI) enhancements. Lack of full support should not prevent you from including them. If a browser doesn’t support CSS transitions, the changes you are attempting to transition will still be applied: they will just “transition” from the initial state to the end state instantaneously when the style recomputation occurs.

Your users may miss out on an interesting (or possibly annoying) effect, but will not miss out on any content.

As transitions are generally progressive enhancements, there is no need to polyfill for archaic IE browsers. While you could use a JavaScript polyfill for IE9 and earlier, and prefix your transitions for Android 4.3 and earlier, there is likely little need to do so.

Transition Properties

In CSS, transitions are written using four transition properties: transition-property, transition-duration, transition-timing-function, and transition-delay, along with the transition property as a shorthand for the four longhand properties.

To create the drop-down navigation from Figure 10-1, we used all four CSS transition properties:

nav li ul {

transition-property: transform;

transition-duration: 200ms;

transition-timing-function: ease-in;

transition-delay: 50ms;

transform: scale(1, 0);

transform-origin: top center;

}

nav li:hover ul {

transform: scale(1, 1);

}

This example defines the transition for our drop-down navigation example in Figure 10-1. The style change in this scenario is caused by hovering over navigational elements.

Note

While we are using the :hover state for our style change event in our transition examples, you can transition properties in other scenarios too. For example, you might add or remove a class, or otherwise change the state—say, by changing an input from :invalid to :valid or from :checked to :not(:checked). Or you might append a table row at the end of a zebra-striped table or list item at the end of a list with styles based on :nth-last-of-type selectors.

In the navigation pictured in Figure 10-1, the initial state of the nested lists is transform: scale(1, 0) with a transform-origin: top center. The final state is transform: scale(1, 1): the transform-origin remains the same.

In this example, the transition properties define a transition on the transform property: when the new transform value is set on hover, the nested unordered list will scale to its original, default size, changing smoothly between the old value of transform: scale(1, 0) and the new value of transform: scale(1, 1) over a period of 200 milliseconds. This transition will start after a 50-millisecond delay, and will ease in, proceeding slowly at first, then picking up speed as it progresses.

Transitions are declared along with the regular styles on an element. Whenever a target property changes, if a transition is set on that property, the browser will apply a transition to make the change gradual. While the most common initiation of a transition is changing property values from a default state to a hovered state, transitions also work if the property is changed by adding a class, manipulating the DOM, or otherwise changing the state.

You can declare transition properties in the initial state, the changed state, or in both the initial and changed states. If you only declare the transition on the initial state, when the state changes, it will transition to the changed state as you indicate with CSS transition properties. If and when it changes back to the initial state, the transition timing is reversed. You can override this default reverse transition by declaring different transitions in both the initial and changed states.

By initial state, I mean a state that matches the element on page load. This could be a state that the element always has, such as properties set on an element selector versus a :hover state for that element, or a content editable element that may get :focus: https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round

/* selector that matches element all the time */

p[contenteditable] {

background-color: rgba(0, 0, 0, 0);

}

/* selector that matches element some of the time */

p[contenteditable]:focus {

/* overriding declaration */

background-color: rgba(0, 0, 0, 0.1);

}

In this example, the fully transparent declaration is always the initial value, changing when the user gives it focus. This is what I mean when I use initial or default value throughout this chapter. The transition properties included in the selector that matches the element all the time will impact that element whenever the state changes, whether it is from the initial state to the focused state (as in the preceding example) or any other altered state, such as a hover state; or when properties are changed with the dynamic addition of a class.

An initial state could also be a temporary state that may change, such as a :checked checkbox or a :valid form control, or even a class that gets toggled on and off:

/* selector that matches element some of the time */

input:valid {

border-color: green;

}

/* selector that matches element some of the time,

when the prior selector does NOT match. */

input:invalid {

border-color: red;

}

/* selector that matches element some of the time,

whether the input is valid or invalid */

input:focus {

/* alternative declaration */

border-color: yellow;

}

In this example, either the :valid or :invalid selector matches, but never both. The :focus selector, as shown in Figure 10-2, matches some of the time, when the input has focus, whether the input is matching the :valid or :invalidselector simultaneously. In this case, when we refer to the initial state, we are referring to the original value, which could be either valid or invalid. In this scenario, the changed state can be the opposite of the initial :valid or :invalid value. The :focus state is another altered state. https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round

taic 0202

Figure 10-2. The input’s appearance in the valid, invalid, and focused states

Generally, you want to declare the transition properties at minimum on the selector that applies to the element all the time. In the preceding contenteditable scenario, it is the first rule. The second scenario is not as clear-cut: if the transition is only set on the :invalid state, the color will transition from red to green as the state of the input changes from invalid to valid, and it will transition from red to yellow when an invalid input receives focus. However, it will not slowly transition from green to yellow, or yellow back to green, when a valid input receives or loses focus. Similarly, the border will transition as it changes from or to the invalid state with a red border; if the input is valid, there will be no transition when the user gives or removes focus.

In this second scenario, we could put the transition on the :focus selector, as generally the value will change from valid to invalid or the reverse when the input has focus. Preferably, however, you will want to put the transition on both the possible initial states, or all three states.

In other words, if you always want the property to transition, you likely want to put the transition on all the states. The transition properties that are used are the ones in the destination state; the new values of the transition properties are used to transition to the new value of the property:

nav li ul {

transition-property: transform;

transition-duration: 200ms;

transition-timing-function: ease-in;

transition-delay: 50ms;

transform: scale(1, 0);

transform-origin: top center;

}

nav li:hover ul {

transition-property: transform;

transition-duration: 2s;

transition-timing-function: linear;

transition-delay: 1s;

transform: scale(1, 1);

}

This provides a horrible user experience, but I’ve included it to show a point. https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round When hovered over, the opening of the navigation takes a full two seconds. When closing, it quickly closes over 0.2 seconds. The transition properties in the destination or hover state are used when hovering over the list item. When no longer hovered over, as it returns to the default scaled-down state, the transition properties of the default properties it is returning to—the nav li ulcondition—are used.

In our example, we don’t want horrible UX. We will omit the slow transition, and instead allow the browser to reverse the transition on mouse out: https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round

nav li ul {

transition-property: transform;

transition-duration: 200ms;

transition-timing-function: ease-in;

transition-delay: 50ms;

transform: scale(1, 0);

transform-origin: top center;

}

nav li:hover ul {

transform: scale(1, 1);

}

To create a simple CSS hover transition, such as the expansion of nested list items in the preceding navigation example, we declare property values in two states: the default or initial state of the element and the hovered state of the element. The initial or original state of the element is declared in the default style declaration. The changed properties, or final or destination state of the element, are declared within a :hover style block. If no transition is set, the nested unordered list scales to its default height instantly on hover.

To transition this expansion, we add the transition functions using the transition-* properties of transition-property, transition-duration, transition-timing-function, and transition-delay, or the transition shorthand. In our example, we will be adding the transition properties only in the default style declaration. When the transition is declared in the style block of the initial state of the element, the transition will be applied as the element changes from the initial state to the changed or destination state and applied in reverse as it changes back to the initial state from the changed state.

In our example, when the user stops hovering over the parent navigational element or the child drop-down menu, the drop-down menu will wait 50 milliseconds before closing over 200 milliseconds, using ease-out as the timing function, reversing the transition declared in the default state. As we saw in our bad UX example, the reverse transition timing function, duration, and delay in the reverting direction can be overridden by providing different transition property values in the default and changed-state style blocks.

While the four transition properties can be declared separately, you will probably always use the shorthand. We’ll take a look at the four properties individually first so you have a good understanding of what each one does, and then we’ll cover the transition shorthand, which is what you will likely use in your code.

Let’s look at the four properties in greater detail.

The transition-property Property

The transition-property property specifies the names of the CSS properties you want to transition. And, yes, it’s weird to say “the transition-property property.”

transition-property

Values:

none | <single-property>#

Initial value:

all

Applies to:

All elements, :before and :after pseudo-elements

Inherited:

No

The value for the transition-property is a comma-separated list of properties; the keyword none if you want no properties transitioned; or the default all, which means “transition all the transitionable properties.” You can also include the keyterm all within a comma-separated list of properties.

If you include all as the only keyterm—or default to all—all the transitionable properties will transition in unison.

Let’s say you want to change a box’s appearance on hover:

div {

color: #ff0000;

border: 1px solid #00ff00;

border-radius: 0;

transform: scale(1) rotate(0deg);

opacity: 1;

box-shadow: 3px 3px rgba(0, 0, 0, 0.1);

width: 50px;

padding: 100px;

}

div:hover {

color: #000000;

border: 5px dashed #000000;

border-radius: 50%;

transform: scale(2) rotate(-10deg);

opacity: 0.5;

box-shadow: -3px -3px rgba(255, 0, 0, 0.5);

width: 100px;

padding: 20px;

}

When the user hovers over the div, every property that has a different value in the default state versus the hovered state will change to the hover-state values. We use the transition-property property to define which of those properties we want to animate over time (versus instantly). All the properties will change from the default value to the hovered value on hover, but only the animatable properties included in the transition-property will transition over time. Nonanimatable properties like border-style will change from one value to the next instantly. See “Transitionable properties”.

all

If you want to define all the properties to transition at the same time, speed, and pace, use all. If all is the only value or the last value in the comma-separated value for transition-property, all the animatable properties will transition in unison.

If we want to transition all the properties, the following statements are almost equivalent:

div {

color: #ff0000;

border: 1px solid #00ff00;

border-radius: 0;

transform: scale(1) rotate(0deg);

opacity: 1;

box-shadow: 3px 3px rgba(0, 0, 0, 0.1);

width: 50px;

padding: 100px;

transition-property: color, border, border-radius, transform, opacity,

box-shadow, width, padding;

transition-duration: 1s;

}

and

div {

color: #ff0000;

border: 1px solid #00ff00;

border-radius: 0;

transform: scale(1) rotate(0deg);

opacity: 1;

box-shadow: 3px 3px rgba(0, 0, 0, 0.1);

width: 50px;

padding: 100px;

transition-property: all;

transition-duration: 1s;

}

Both transition-property property declarations will transition all the properties listed—but the former will transition only the eight properties that may change, based on property declarations that may be included in other rule blocks. Those eight property values are included in the same rule block, but they don’t have to be.

The all in the latter example ensures that all animatable property values that would change based on any style change event—no matter which CSS rule block includes the changed property value—transitions over one second. The transition applies to all animatable properties of all elements matched by the selector, not just the properties declared in the same style block as the all.

Declaring individual properties means only the properties specifically defined in the value of the transition-property transition when the value gets changed—whether those property values are inherited, declared in the same rule block, or applied to the element via a different CSS rule block.

In this case, the first version limits the transition to only the eight properties listed, but enables us to provide more control over how each property will transition. Declaring the properties individually lets us provide different speeds, delays, and/or durations to each property’s transition if we declared those transition properties separately:

<div class="foo">Hello</div>

div {

color: #ff0000;

border: 1px solid #0f0;

border-radius: 0;

transform: scale(1) rotate(0deg);

opacity: 1;

box-shadow: 3px 3px rgba(0, 0, 0, 0.1);

width: 50px;

padding: 100px;

}

.foo {

color: #00ff00;

transition-property: color, border, border-radius, transform, opacity,

box-shadow, width, padding;

transition-duration: 1s;

}

The transition-property property does not need to be in the same rule block as the properties that make up its value.

If you want to define the transitions for each property separately, write them all out, separating each of the properties with a comma. If you want to animate almost all the properties at the same time, delay, and pace, with a few exceptions, you can use a combination of all and the individual properties you want to transition at different times, speeds, or pace. Make sure to use all as the first value:

div {

color: #f00;

border: 1px solid #00ff00;

border-radius: 0;

transform:

scale(1) rotate(0deg);

opacity: 1;

box-shadow: 3px 3px rgba(0, 0, 0, 0.1);

width: 50px;

padding: 100px;

transition-property: all, border-radius,

opacity; transition-duration: 1s, 2s, 3s;

}

The all part of the comma-separated value includes all the properties listed in the example, as well as all the inherited CSS properties, and all the properties defined in any other CSS rule block matching or inherited by the element. In the preceding example, all the properties getting new values will transition at the same duration, delay, and timing function, with the exception of border-radius and opacity, which we’ve explicitly included separately. Because we included them as part of a comma-separated list after the all, we can transition them at the the same time, delay, and timing function as all the other properties, or we can provide different times, delays, and timing functions for these two properties. In this case, we transition all the properties over one second, except for border-radius and opacity, which we transition over two seconds and three seconds, respectively. We cover transition-duration next.

Note

Make sure to use all as the first value in your comma-separated value list, as the properties declared before the all will be included in the all, overriding any other transition property values you intended to apply to those now overridden properties.

none

While transitioning over time doesn’t happen by default, if you do include a CSS transition and want to override that transition in a particular scenario, you can set transition-property: none to override the entire transition and ensure no properties are transitioned. The none; keyword can only be used as a unique value of the property—you can’t include it as part of a comma-separated list of properties. If you want to override the animation of fewer than all the properties, you will have to list all of the properties you still want to transition. You can’t use the transition-property property to exclude properties; rather, you can only use that property to include them. Another trick would be to set the delay and duration of the property to 0s. That way it will appear instantaneously: no CSS transition is being applied to it.

Transitionable properties

Not all properties are transitionable, and not all values of some normally transitionable properties can be transitioned. There is a finite list of CSS 2.1 properties that are animatable, which is summarized in “Animatable Properties”. Realize that as CSS is evolving, new properties are being added. While the animatable properties list is not worth memorizing, the general rule is that if there is a logical midpoint between the initial value and the final value of a property, that property and value type is probably animatable.

By “property and value type,” I mean that some properties are animatable, but not all values of those properties are animatable. Numeric values tend to be animatable; keyword values that can’t be converted to numeric values generally aren’t. Keywords that represent computed values, like red (which is converted to an RGB value) are animatable. Keyterms that aren’t computed values, like auto in top: auto, are not. CSS functions that take numeric values as parameters generally are animatable. For example, you can transition from height: 0 to height: 200px as both values are numeric. But even though height is an animatable property, height: auto is not an animatable value, as auto in this case is not a computed value.

You can transition from color: red to color: slategray, as the browser converts the colors from named colors to hexadecimal values, which are numeric; the browser can determine the midpoint between two numeric values.

If you accidentally included a property that can’t be transitioned, fear not. The browser will simply not transition the property that is not animatable. The entire declaration will not fail. The nonanimatable property or nonexistent CSS property is not exactly ignored. The browser passes over unrecognized or nonanimatable properties, keeping their place in the property list order to ensure that the other comma-separated transition properties described next are not applied on the wrong properties.4

Note

Transitions can only occur on properties that are not currently being impacted by a CSS animation. If the element is being animated, properties may still transition, as long as they are not properties that are currently controlled by the animation. CSS animations are covered in Chapter 11.

The behavior of transitions seemingly not adhering to the basics of CSS cascades when an animation on the same element and property is running does not affect whether the transition has started or ended. The cascade is actually being adhered to. Transition events will still fire, confirming the transition occurred.

The length of the transition-property list determines the number of items in the transition-duration, transition-timing-function, and transition-delay lists. If the number of values in any or all of these three properties does not match the number of values listed in the transition-property value, the browser will ignore any excess values, or repeat values when these other properties have fewer values in their comma-separated list than the transition-property property. For this reason, the order of the values in the transition-property value may be important, just as it is important for other transition properties.

If you include a property that is not animatable (like a border-style value change) or a nonexistent property (such as a property name with a typo in it), the transition-property will still work. Unrecognized words or properties that are not animatable are not ignored. Rather, they are kept in the list of properties to ensure that values from other comma-separated transition properties, such as transition-duration, are applied in the right order.

However, if you have a syntax error, like a missing comma between two property names or a space within a property name, that transition-property property declaration will be ignored. Similarly, if you include the terms none, inherit, or initial, as per the specification, the entire property exists, but fails, so should be ignored. This is not the case in some browsers, however. Safari 8 and IE Edge 12 treat none, inherit, and initial in a list of comma-separated properties as unrecognized or nonanimatable properties.

Transition event: transitionend

A transitionend event occurs at the end of every transition, in each direction, for every property that is transitioned over any amount of time or after any delay, whether the property is declared individually or is part of the alldeclaration. For some seemingly single property declarations, there will be several transitionend events, as every animatable property within a shorthand property gets its own transitionend event.

In the preceding example, when the transition concludes, there will be well over eight transitionend events. For example, the border-radius transition alone produces four transitionend events, one each for:

border-bottom-left-radius

border-bottom-right-radius

border-top-right-radius

border-top-left-radius

The padding property is also a shorthand for four longhand properties:

padding-top

padding-right

padding-bottom

padding-left

The border shorthand property produces eight transitionend events: four values for border-width and four for border-color, both of which are shorthand declaration themselves:

border-left-width

border-right-width

border-top-width

border-bottom-width

border-top-color

border-left-color

border-right-color

border-bottom-color

There are no transitionend events for border-style properties, as border-style is not an animatable property.

How do we know it’s not animatable? We can assume it isn’t, since there is no logical midpoint between the two values of solid and dashed. We can confirm by looking up the list of animatable properties or the specifications for the individual properties.

There will be 21 transitionend events in our scenario in which 8 specific properties are listed, as those 8 include several shorthand properties that have different values in the pre and post states. In the case of all, there will be at least 21 transitionend events: one for each of the longhand values making up the 8 properties we know are included in the pre and post states, and possibly from others that are inherited or declared in other style blocks impacting the element: https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round

document.querySelector('div').addEventListener('transitionend',

function (e) {

console.log(e.propertyName);

});

The transitionend event includes three event specific attributes: 1) propertyName, which is the name of the CSS property that just finished transitioning; 2) pseudoElement, which is the pseudoelement upon which the transition occurred, preceded by two semicolons, or an empty string if the transition was on a regular DOM node; and 3) elapsedTime, which is the amount of time the transition took to run, in seconds, which is generally the time listed in the transition-duration property.

The transitionend event only occurs if the property successfully transitions to the new value. The transitioned event doesn’t fire if the transition was interrupted by another change to the same property on the same element.

When the properties return to their initial value, another transitionend event occurs. This event occurs as long as the transition started, even if it didn’t finish transitioning in the original direction.

The transition-duration Property

The transition-duration property takes as its value a comma-separated list of lengths of time, in seconds (s) or milliseconds (ms), it should take to transition from the original property values to the final property values.

transition-duration

Values:

<time>#

Initial value:

0s

Applies to:

All elements, :before and :after pseudo-elements

Inherited:

No

The transition-duration property dictates how long it should take for each property to transition from the original value to the new value. If reverting between two states, and the duration is only declared in one of those states, the transition will take the amount of time declared to revert to the previous state:

input:invalid {

transition-duration: 1s;

background-color: red;

}

input:valid {

transition-duration: 0.2s;

background-color: green;

}

If different values for the transition-duration are declared, the duration of the transition will be the transition-duration value declared in the rule block it is transitioning to. In the preceding example, it will take 1 second for the input to change to a red background when it becomes invalid, and only 200 milliseconds to transition to a green background when it becomes valid. https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round

The value of the transition-duration property should be declared as a positive value in either seconds (s) or milliseconds (ms). The time unit of ms or s is required by the specification, even if the duration is set to 0s. By default, properties simply change from one value to the next instantly. In line with this, the default value for the duration of a transition is 0s, meaning the transition is immediate, showing no animation.

Unless there is a positive value for transition-delay set on a property, if transition-duration is omitted, it is as if no transition-property declaration had been applied—with no transitionend event occuring. As long as the total time set for a transition to occur is greater than 0s—which can happen with a duration of 0s or when the transition-duration is omitted and defaults to 0s, if there is a positive transition-delay value—the transition will still be applied and a transitionend event will occur if the transition finishes.

Negative values for transition-duration are invalid, and, if included, will invalidate the entire property value.

Using the same super-long transition-property declaration, we can declare a single duration for all the properties or individual durations for each property, or we can make alternate properties animate for the same length of time. We can declare a single duration that applies to all properties during the transition by including a single transition-duration value:

div {

color: #ff0000;

...

transition-property: color, border, border-radius, transform, opacity,

box-shadow, width, padding;

transition-duration: 200ms;

}

We could have instead declared the same number of comma-separated time values for the transition-duration property value as the CSS properties we enumerated in the transition-property property value. If we want each property to transition over a different length of time, we have to include a different comma-separated value for each property name declared:

div {

color: #ff0000;

...

transition-property: color, border, border-radius, transform, opacity,

box-shadow, width, padding;

transition-duration: 200ms, 180ms, 160ms, 140ms, 120ms, 100ms, 1s, 2s;

}

If the number of properties declared does not match the number of durations declared, the browser has specific rules on how to handle the mismatch. If there are more durations than properties, the extra durations are ignored. If there are more properties than durations, the durations are repeated. In this example, color, border-radius, opacity, and width have a duration of 100 ms; border, transform, box-shadow, and padding will be set to 200 ms:

div {

...

transition-property: color, border, border-radius, transform, opacity,

box-shadow, width, padding;

transition-duration: 100ms, 200ms;

}

If we declare exactly two comma-separated durations, every odd property will transition over the first time declared, and every even property will transition over the second time value declared.

If a transition is too slow, the website will appear slow or unresponsive, drawing unwanted focus to what should be a subtle effect. If a transition is too fast, it may be too subtle to be noticed. While you can declare any positive length of time you want for your transitions, your goal is likely to provide an enhanced rather than annoying user experience. Effects should last long enough to be seen, but not so long as to be noticeable. Generally, the best effects range between 100 and 200 milliseconds, creating a visible, yet not distracting, transition.

We want a good user experience for our drop-down menu, so we set both properties to transition over 200 milliseconds:

nav li ul {

transition-property: transform, opacity;

transition-duration: 200ms;

...

}

The transition-timing-function Property

Do you want your transition to start off slow and get faster, start off fast and end slower, advance at an even keel, jump through various steps, or even bounce? The transition-timing-function provides a way to control the pace of the transition. The transition-timing-function property describes how the transition proceeds as it is being executed.

transition-timing-function

Values:

<timing-function>#

Initial value:

ease

Applies to:

All elements, :before and :after pseudo-elements

Inherited:

No

The transition-timing-function values include ease, linear, ease-in, ease-out, ease-in-out, step-start, step-end, steps(n, start)—where n is the number of steps—steps(n, end), and cubic-bezier(x1, y1, x2, y2). These values are also the valid values for the animation-timing-function and are described in great detail in Chapter 11.

The non-step keyword are easing timing functions employing cubic Bézier mathematical functions to provide smooth curves. The specification provides for five predefined easing functions, but you can describe your own precise timing function by defining your own cubic-bezier() function, as shown in Table 10-1.

Table 10-1. Supported keyterms for cubic Bézier timing functions

Timing function

Definition

cubic-bezier value

ease

Starts slow, then speeds up, then ends very slowly

cubic-bezier(0.25, 0.1, 0.25, 1)

linear

Proceeds at the same speed throughout transition

cubic-bezier(0, 0, 1, 1)

ease-in

Starts slow, then speeds up

cubic-bezier(0.42, 0, 1, 1)

ease-out

Starts fast, then slows down

cubic-bezier(0, 0, 0.58, 1)

ease-in-out

Similar to ease; faster in the middle, with a slow start but not as slow at the end

cubic-bezier(0.42, 0, 0.58, 1)

cubic-bezier()

Specifies a cubic-bezier curve

cubic-bezier(x1, y1, x2, y2)

Cubic Bézier curves, including the underlying curves defining the five named easing functions defined in Table 10-1 and displayed in Figure 10-3, take four numeric parameters. For example, linear is the same as cubic-bezier(0, 0, 1, 1). The first and third cubic Bézier function parameter values need to be between 0 and +1.

taic 0203

Figure 10-3. Supported cubic Bézier named functions incude ease, linear, ease-in, ease-out, and ease-in-out

If you’ve taken six years of calculus, the method of writing a cubic Bézier function might make sense; otherwise, it’s likely you’ll want to stick to one of the five basic timing functions. There are online tools that let you play with different values, such as cubic-bezier.com, which lets you compare the common keywords against each other, or against your own cubic Bézier function.

The predefined key terms are fairly limited. To better follow the principles of animation (refer back to “12 Basic Principles of Animation”), you may want to use a cubic Bézier function with four float values instead of the predefined key words.

As shown in Figure 10-4, the website easings.net provides many additional cubic Bézier function values you can use to provide for a more realistic, delightful animation.

taic 0204

Figure 10-4. Useful author-defined cubic Bézier functions

While the authors of the site named their animations, the preceding names are not part of the CSS specifications, and must be written as follows:

Unofficial name

Cubic Bézier function value

easeInSine

cubic-bezier(0.47, 0, 0.745, 0.715)

easeOutSine

cubic-bezier(0.39, 0.575, 0.565, 1)

easeInOutSine

cubic-bezier(0.445, 0.05, 0.55, 0.95)

easeInQuad

cubic-bezier(0.55, 0.085, 0.68, 0.53)

easeOutQuad

cubic-bezier(0.25, 0.46, 0.45, 0.94)

easeInOutQuad

cubic-bezier(0.455, 0.03, 0.515, 0.955)

easeInCubic

cubic-bezier(0.55, 0.055, 0.675, 0.19)

easeOutCubic

cubic-bezier(0.215, 0.61, 0.355, 1)

easeInOutCubic

cubic-bezier(0.645, 0.045, 0.355, 1)

easeInQuart

cubic-bezier(0.895, 0.03, 0.685, 0.22)

easeOutQuart

cubic-bezier(0.165, 0.84, 0.44, 1)

easeInOutQuart

cubic-bezier(0.77, 0, 0.175, 1)

easeInQuint

cubic-bezier(0.755, 0.05, 0.855, 0.06)

easeOutQuint

cubic-bezier(0.23, 1, 0.32, 1)

easeInOutQuint

cubic-bezier(0.86, 0, 0.07, 1)

easeInExpo

cubic-bezier(0.95, 0.05, 0.795, 0.035)

easeOutExpo

cubic-bezier(0.19, 1, 0.22, 1)

easeInOutExpo

cubic-bezier(1, 0, 0, 1)

easeInCirc

cubic-bezier(0.6, 0.04, 0.98, 0.335)

easeOutCirc

cubic-bezier(0.075, 0.82, 0.165, 1)

easeInOutCirc

cubic-bezier(0.785, 0.135, 0.15, 0.86)

easeInBack

cubic-bezier(0.6, -0.28, 0.735, 0.045)

easeOutBack

cubic-bezier(0.175, 0.885, 0.32, 1.275)

easeInOutBack

cubic-bezier(0.68, -0.55, 0.265, 1.55)

There are also step timing functions available, with two predefined step values:

Timing function

Definition

step-start

Stays on the final keyframe throughout transition. Equal to steps(1, start).

step-end

Stays on the initial keyframe throughout transition. Equal to steps(1, end).

steps(n, start)

Displays n stillshots, where the first stillshot is n/100 percent of the way through the transition.

steps(n, end)

Displays n stillshots, staying on the initial values for the first n/100 percent of the time.

As Figure 10-5 shows, the stepping functions show the progression of the transition from the initial value to the final value in steps, rather than as a smooth curve.

taic 0205

Figure 10-5. Step timing functions

The step functions allow you to divide the transition over equidistant steps. The functions define the number and direction of steps. There are two direction options: start and end. With start, the first step happens at the animation start. With end, the last step happens at the animation end. For example, steps(5, end) would jump through the equidistant steps at 0%, 20%, 40%, 60%, and 80%; and steps(5, start) would jump through the equidistant steps at 20%, 40%, 60%, 80%, and 100%.

The step-start function is the same as steps(1, start). When you use it, the property value stays on the final value from the beginning until the end of the transition. The step-end function, which is the same as steps(1, end), sits on the initial value of the property, staying there through the transition’s duration.

Continuing on with the same super-long transition-property declaration, we can declare a single timing function for all the properties or individual timing functions for each property, or we can make every even property have one timing function, while every odd property proceeds at a separate pace:

div {

...

transition-property: color, border-width, border-color, border-radius,

transform, opacity, box-shadow, width, padding;

transition-duration: 200ms;

transition-timing-function: ease-in;

}

In the preceding example, we made all the properties transition at the same tempo by including a single time as the timing-function value:

div {

...

transition-property: color, border-width, border-color, border-radius,

transform, opacity, box-shadow, width, padding;

transition-duration: 200ms, 180ms, 160ms, 140ms, 120ms, 100ms, 1s, 2s, 3s;

transition-timing-function: ease, ease-in, ease-out, ease-in-out, linear,

step-end, step-start, steps(5, start), steps(3, end);

}

We can also create a horrible user experience by making every property transition at a different rhythm. The transition-timing-function does not change the time it takes to transition properties: that is set with the transition-duration property; but it does change how the transition progresses during that set time:

div {

...

transition-property: color, border-width, border-color, border-radius,

transform, opacity, box-shadow, width, padding;

transition-duration: 200ms;

transition-timing-function: ease, ease-in, ease-out, ease-in-out, linear,

step-end, step-start, steps(5, start), steps(3, end);

}

If we include these nine different timing functions for the nine different properties, as long as they have the same transition duration and delay, all the properties start and finish transitioning at the same time. The timing function controls how the transition progresses, but does not alter the time it takes for the transition to finish.

These timing functions are described in great detail in “animation-timing-function”. The best way to familiarize yourself with the timing functions is to play with them and see which one works best for the effect you’re looking for. While testing, set a relatively long transition-duration to better visualize the difference5 between the various functions. At higher speeds, you may not be able to tell the difference with the easing function; just don’t forget to set it back to under 200 milliseconds before launching your website:

nav li ul {

transition-property: transform, opacity;

transition-duration: 200ms;

transition-timing-function: ease-in;

...

}

Our navigation example has transitions occurring in two directions: one transition occurs when changing from the default or initial value to the final hovered value. The second transition occurs when the user mouses off the list item and the nested unordered list returns to its previous or initial state. When the user hovers over the drop-down navigation, it transitions open; it transitions closed when the user mouses off.

We want our menu to open and become opaque fairly quickly, while appearing gradual. The ease-in value is the best for this. The timing function is reversed in the reverse direction; by default, when returning to the initial values, the transition will run in reverse order, inverting the timing function. It therefore eases in as it opens and eases out as it closes. In this example, ease-in was used, so the return trip will appear as if it was set to ease-out as it proceeds in the opposite direction, going from open to closed. This is the default behavior, but it can be controlled. Controlling the reverse transition direction is discussed in “In Reverse: Transitioning Back to Baseline”.

The transition-delay Property

The transition-delay property enables you to introduce a time delay between when the change that initiates the transition is applied to an element and when the transition begins.

If you hover over an element that has a color change on hover without a transition, the color will change immediately. Similarly, a transition-delay of 0s (the default) means the transition will begin immediately—it will start executing as soon as the state of the element is altered. Otherwise, the time value of the transition-delay defines the time offset from the moment the property values would have changed (had no transition or transition-propertybeen applied) until the property values declared in the transition or transition-property value begins animating to the next value.

transition-delay

Values:

<time>#

Initial value:

0s

Applies to:

All elements, :before and :after pseudo-elements

Inherited:

No

Including a transition-delay with a positive number of milliseconds (ms) or seconds (s) to delay the transition will delay the onset of the transition effect. The time unit, as s or ms, is required. Negative values of time are valid. The effects you can create with negative transition-delays are described in “Negative values”.

Tip

Unlike transition-duration, negative time values are allowed for the transition-delay property.

Continuing with the 8- (or 21-) property transition-property declaration, we can make all the properties start transitioning right away by omitting the transition-delay property or including it with a value of 0s—but that’s not a very interesting example. For the sake of examples, we could delay the start of all the even-numbered properties, while all the odd-numbered properties start right away, by including two comma-separated values, starting with 0s or 0ms:

div {

...

transition-property: color, border, border-radius, transform, opacity,

box-shadow, width, padding;

transition-duration: 200ms;

transition-timing-function: linear;

transition-delay: 0s, 200ms;

}

By including transition-delay: 0s, 200ms on a series of properties, each taking 200 milliseconds to transition, we make every odd-numbered property begin its transition immediately; all the even-numbered transitions begin their transitions as soon as the odd transitions have completed.

As with transition-duration and transition-timing-function, when the number of comma-separated transition-delay values outnumbers the number of comma-separated transition-property values, the extra delay values are ignored. When the number of comma-separated transition-property values outnumbers the number of comma-separated transition-delay values, the delay values are repeated. In this case, with only two values, the first value (0s) is applied to each odd property, providing no delay, and the second value is applied to every even property, providing a 200-millisecond delay. Because we declared the transition-duration as 200ms in this scenario, every evenly numbered property will begin transitioning after 200 milliseconds, which is after every oddly numbered property has finished transitioning:

div {

...

transition-property: color, border-width, border-color, border-radius,

transform, opacity, box-shadow, width, padding;

transition-duration: 200ms;

transition-timing-function: linear;

transition-delay: 0s, 0.2s, 0.4s, 0.6s, 0.8s, 1s, 1.2s, 1.4s, 1.6s;

}

We can even declare nine different transition-delay values so that each property begins transitioning after the previous property has transitioned. In this example, we declared each transition to last 200 milliseconds with the transition-duration property. We then declare a transition-delay that provides comma-separated delay values for each property that increment by 200 milliseconds, or 0.2 seconds—the same time as the duration of each property’s transition. That means we can make each property start transitioning as soon as the previous property has finished.

We can use math to give every transitioning property different durations and delays, ensuring they all complete transitioning at the same time:

div {

...

transition-property: color, border-width, border-color, border-radius,

transform, opacity, box-shadow, width, padding;

transition-duration: 1.8s, 1.6s, 1.4s, 1.2s, 1s, 0.8s, 0.6s, 0.4s, 0.2s;

transition-timing-function: linear;

transition-delay: 0s, 0.2s, 0.4s, 0.6s, 0.8s, 1s, 1.2s, 1.4s, 1.6s;

}

In this example, each property completes transitioning at the 1.8-second mark, but each with a different duration and delay. For each property, the transition-duration value plus the transition-delay value will add up to 1.8 seconds:

div {

...

transition-property: color, border-width, border-color, border-radius,

transform, opacity, box-shadow, width, padding;

transition-duration: 200ms;

transition-timing-function: linear;

transition-delay: 50ms;

}

Generally, you want all the transitions to begin at the same time. You can make that happen by including a single transition-delay value, which gets applied to all the properties. In our drop-down menu in Figure 10-1, we include a delay of 50 milliseconds. This delay is not long enough for the user to notice and will not cause the application to appear slow. Rather, a 50-millisecond delay can help prevent the navigation from shooting open unintentionally as the user accidentally passes over, or hovers over, the menu items while moving the cursor from one part of the page or app to another.

Negative values

A negative time value for transition-delay will make the transition begin immediately, partially through the transition. A negative transition-delay that is smaller than the transition-duration will cause the transition to start immediately, partway through the transition: https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round

div {

transform: translateX(0);

transition-property: transform;

transition-duration: 200ms;

transition-delay: -150ms;

transition-timing-function: linear;

}

div:hover {

transform: translateX(200px);

}

For example, if you have a transition-delay of -150ms on a 200ms transition, the transition will start three-quarters of the way through the transition and will last 50 milliseconds. In that scenario, with a linear timing function, it will jump to being translated 150px along the x-axis immediately on hover and then animate the translation from 150 pixels to 200 pixels over 50 milliseconds.

If the absolute value of the negative transition-delay is greater than or equal to the transition-duration, the change of property values is immediate, as if no transition had been applied, and no transitionend event occurs.

When transitioning back from the hovered state to the original state, by default, the same value for the transition-delay is applied. In the preceding scenario, with the transition-delay not being overridden in the hover state, it will jump 75% of the way back (or 25% of the way through the original transition) and then transition back to the initial state. On mouseout, it will jump to being translated 50 pixels along the x-axis and then take 50 milliseconds to return to its initial position of being translated 0 pixels along the x-axis.

Improving user experience

If you hover over the navigation item from Figure 10-1, you would expect the drop-down menu to appear immediately. But that isn’t the user experience we want. The user may unintentionally hover over the navigation while mousing from one section of the document to another. Waiting for the user to hover over the navigation element for 50 milliseconds before opening the drop-down menu isn’t enough of a delay to make the site appear slow but is enough of a delay to ensure menus don’t seem to be unintentionally flying open:

nav li ul {

transition-property: transform, opacity;

transition-duration: 200ms;

transition-timing-function: ease-in;

transition-delay: 50ms;

transform: scale(1, 0);

transform-origin: top center;

opacity: 0;

}

nav li:hover ul {

transform: scale(1, 1);

opacity: 1;

}

In our navigation example, we add a 50-millisecond transition delay. This way, our drop-down menu won’t transition immediately if the user accidentally mouses over a link on the way from one part of the document to another. By adding transition-delay: 50ms, we can be more confident the user is intentionally hovering over the parent navigation item before opening the drop-down menu.

The browser will also wait 50 milliseconds after the user mouses off the navigational element before transitioning back to the pretransitioned state. When the transition-delay is specified somewhere that applies to the element all the time, the transition-delay occurs in both transition directions; that means the browser will wait 50 milliseconds after the parent li or its descendant loses hover before closing the drop-down menu.

This 50-millisecond delay before closing occurs whether or not the menu is completely open or even if the user mouses out of the menu before the menu finishes transitioning open. The browser will wait 50 milliseconds before opening the drop-down menu and will also wait 50 milliseconds before closing it—whether or not it was ever fully visible—as long as it had started to open.

This may seem odd, but it improves user experience. Often users accidentally mouse out of a navigational element as they mouse toward an item in the newly opened submenu. This 50-millisecond delay in the reverse direction gives the user a 50-millisecond window to get back onto the drop-down menu before it closes. This isn’t enough time to completely hover off and back on, but if there are submenus, an accidental mousing over nonnavigation space as the user moves the pointer to a subnavigation may be short enough to not close the navigation completely. If the user doesn’t hover over the open menu of the parent tab, the menu will transition back to a closed state. This is a good user experience.

If a mouse user leaves the area after the 50-millisecond delay but before the 200-millisecond duration, the menu will not open fully. Rather, there will be a 50-millisecond delay, and then the menu will revert to its fully closed state. Some browsers will take the full 200 milliseconds to revert; others will spend the same amount of time in the reverse direction as they did in the normal direction. A reversing shortening factor, which shortens the reverse transition time of incomplete transitions, is defined in the CSS Transitions specifications and is beginning to be implemented in browsers.

Reverse direction

When a transitioned property reverts from the final state to the initial state and transition properties are only set on the start or initial state, the delay is repeated and the timing is reversed. If the transition is interrupted and doesn’t complete, the duration and delay are not ignored as the properties revert.

When a transition is interrupted before it is able to finish (such as mousing off of our drop-down menu example before it finishes opening), property values are reset to the values they had before the transition began, and the properties will transition back to those values. Because repeating the duration and timing functions on a reverting partial transition can lead to an odd or even bad user experience, the CSS transitions specification provides for making the reverting transition shorter.

In our menu example, we have a transition-delay of 50ms set on the default state and no transition properties declared on the hover state; thus, browsers will wait 50 milliseconds before beginning the reverse or closing transition.

When the forward animation finishes transitioning to the final values and the transitionend event is fired, all browsers will duplicate the transition-delay in the reverse states.

As Table 10-2 shows, if the transition didn’t finish—say, if the user moved off the navigation before the transition finished—all browsers except Microsoft Edge will repeat the delay in the reverse direction. Some browsers replicate the transition-duration as well, but Edge and Firefox have implemented the specification’s reversing shortening factor.

Table 10-2. Unfinished transition reverse behavior by browser

Browser

Reverse delay

Transition time

Elapsed time

Chrome 37

Yes

200 ms

0.200s

Chrome 42

Yes

200 ms

0.250s

Safari 8

Yes

200 ms

0.200s

Firefox 41

Yes

38 ms

0.038s

Opera 32

Yes

200 ms

0.250s

Edge 12

No

38 ms

0.038s

Let’s say the user moves off that menu 75 milliseconds after it started transitioning. This means the drop-down menu will animate closed without ever being fully opened and fully opaque. The browser should have a 50-millisecond delay before closing the menu, just like it waited 50 milliseconds before starting to open it. This is actually a good user experience, as it provides a few milliseconds of delay before closing, preventing jerky behavior if the user accidentally navigates off the menu. As shown in Table 10-2, all browsers do this, except Microsoft Edge. In cases where the original transition has completed, all browsers, including Edge, will repeat the 50-millisecond delay before reverting the transition and closing the menu—but if the original transition did not have time to conclude, Microsoft Edge currently does not wait before reversing the transition. This is true for positive transition-delay values.

Even though we only gave the browser 75 milliseconds to partially open the drop-down menu before closing the menu, some browsers will take 200 milliseconds—the full value of the transition-duration property—to revert. Other browsers, including Firefox and Edge, have implemented the CSS specification’s reversing shortening factor and the reversing-adjusted start value. When implemented, the time to complete the partial transition in the reverse direction will be similar to the original value, though not necessarily exact. For step timing functions, it will be the time it took to complete the last completed step. For linear timing functions, the partial durations will be the same in both directions. In the case of our ease-in 75-millisecond partial transition duration, the reverse duration is 38.4 milliseconds:

div {

width: 100px;

transition: width 10s steps(10, start);

}

div:hover {

width: 200px;

}

In the case of a steps timing function, Firefox and Edge will take the time, rounded down to the number of steps the function has completed. For example, if the transition was 10 seconds with 10 steps, and the properties reverted after 3.25 seconds, ending a quarter of the way between the third and fourth steps (completing 3 steps, or 30% of the transition) it will take 3 seconds to revert to the previous values. In the preceding example, the width of our divwill grow to 130 pixels wide before it begins reverting back to 100 pixels wide on mouseout.

While the reverse duration will be rounded down to the time it took to the last step, the reverse direction will be split into the originally declared number of steps, not the number of steps that completed. In our 3.25-second case, it will take 3 seconds to revert through 10 steps. These reverse transition steps will be shorter in duration at 300 milliseconds each, each step shrinking the width by 3 pixels, instead of 10 pixels.

If we were animating a sprite by transitioning the background-position https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round, this would look really bad. The specification and implementation may change to make the reverse direction take the same number of steps as the partial transition. Other browsers currently take 10 seconds, reverting the progression of the 3 steps over 10 seconds across 10 steps—taking a full second to grow the width in 3-pixel steps.

Browsers that haven’t implemented shortened reversed timing, including Chrome, Safari, and Opera, will take the full 10 seconds, instead of only 3, splitting the transition into 10 steps, to reverse the 30% change. Whether the initial transition completed or not, these browsers will take the full value of the initial transition duration, less the absolute value of any negative transition-delay, to reverse the transition, no matter the timing function. In the steps case just shown, the reverse direction will take 10 seconds. In our navigation example, it will reverse over 200 milliseconds, whether the navigation has fully scaled up or not.

For browsers that have implemented the reversing timing adjustments, if the timing function is linear, the duration will be the same in both directions. If the timing function is a step function, the reverse duration will be equal to the time it took to complete the last completed step. All other cubic-bezier functions will have a duration that is proportional to progress the initial transition made before being interrupted. Negative transition-delay values are also proportionally shortened. Positive delays remain unchanged in both directions.

No browser will have a transitionend for the hover state, as the transition did not end; but all browsers will have a transitionend event in the reverse state when the menu finishes collapsing. The elapsedTime for that reverse transition depends on whether the browser took the full 200 milliseconds to close the menu, or if the browser takes as long to close the menu as it did to partially open the menu. Chrome and Opera include the delay in their elapsedTime value. As of early 2016, this is a bug, and should be fixed soon. All other browsers include only the time the browser spent transitioning back—75 milliseconds for Firefox and Edge, 200 milliseconds for Safari and older versions of Chrome and Android.

To override these values, include transition properties in both CSS rule blocks. While this does not impact the reverse shortening, it does provide more control.

We’ll first cover the transition shorthand property, then we’ll use that property to set different transitions in the reverse direction.

The transition Shorthand Property

The transition shorthand property combines the four properties just described—transition-property, transition-duration, transition-timing-function, and transition-delay—into a single property.

transition

Values:

<single-transition>#

Initial value:

all 0s ease 0s

Applies to:

All elements, :before and :after pseudo-elements

Inherited:

No

The transition property accepts the value of none or any number of comma-separated list of single transitions. A single transition contains a single property to transition, or the keyword all to transition all the properties—preferably the duration for the transition, and optionally, the timing function and delay.

If a single transition within the transition shorthand omits the property to transition (or the keyword all), the single transition will default to all. If the transition-timing-function value is omitted, it will default to ease. If only one time value is included, that will be the duration, and there will be no delay, as if transition-delay were set to 0s. If two time values are included, the first is the transition-duration and the second is the transition-delay.

Within each single transition, the order of the duration versus the delay is important: the first value that can be parsed as a time will be set as a duration. If an additional time value is found before the comma or the end of the statement, that will be set as the delay:

nav li ul {

transition: transform 200ms ease-in 50ms,

opacity 200ms ease-in 50ms;

...

}

nav li ul {

transition: all 200ms ease-in 50ms;

...

}

nav li ul {

transition: 200ms ease-in 50ms;

...

}

The shorthand for our drop-down menu can be written three different ways. In the first example, we included shorthand for each of the two properties. Because we are transitioning all the properties that change on hover, we could use the keyword all, as shown in the second example. And, as all is the default value, we could write the shorthand with just the duration, timing-function and delay. Had we used ease instead of ease-in, we could have omitted the timing function, since ease is the default.

We had to include the duration, or no transition would be visible. In other words, the only portion of the transition property that can be considered required is transition-duration.

If we simply wanted to delay the change from closed menu to open menu without a gradual transition, we would still need to include a duration of 0s. Remember, the first value parsable as time will be set as the duration, and the second one will be set as the delay:

nav li ul {

transition: 0s 200ms; ...

Warning

This navigation will wait 200 milliseconds, then show the drop-down fully open and opaque with no gradual transition. This is horrible user experience. Though if you switch the selector from nav li ul to *, it might make for an April Fools’ joke.

If there is a comma-separated list of transitions (versus just a single declaration) and the word none is included, the entire transition declaration is invalid and will be ignored:

div {

...

transition-property: color, border-width, border-color, border-radius,

transform, opacity, box-shadow, width, padding;

transition-duration: 200ms, 180ms, 160ms, 140ms, 120ms, 100ms, 1s, 2s, 3s;

transition-timing-function: ease, ease-in, ease-out, ease-in-out, linear,

step-end, step-start, steps(5, start), steps(3, end);

transition-delay: 0s, 0.2s, 0.4s, 0.6s, 0.8s, 1s, 1.2s, 1.4s, 1.6s;

}

div {

...

transition:

color 200ms,

border-width 180ms ease-in 200ms,

border-color 160ms ease-out 400ms,

border-radius 140ms ease-in-out 600ms,

transform 120ms linear 800ms,

opacity 100ms step-end 1s,

box-shadow 1s step-start 1.2s,

width 2s steps(5, start) 1.4s,

padding 3s steps(3, end) 1.6s;

}

The two preceding CSS rule blocks are functionally equivalent: you can declare comma-separated values for the four longhand transition properties, or you can include a comma-separated list of single transitions. You can’t, however, mix the two: transition: transform, opacity 200ms ease-in 50ms will ease in the opacity over 200 milliseconds after a 50-millisecond delay, but the transform change will be instantaneous, with no transitionend event.

Note the duration comes before the delay in all the single transitions. Also note the first single transition omits the delay and timing-function, as the values they’re mapped to in the longhand syntax version are the properties’ default values.

In Reverse: Transitioning Back to Baseline

In the preceding examples, we’ve declared a single transition. All our transitions have been applied in the default state and initiated with a hover. With these declarations, the properties return back to the default state via the same transition on mouseout, with a reversing of the timing function and a duplication of the delay.

With transition declarations only in the global state, both the hover and mouseout states use the same transition declaration: the selector matches both states. We can override this duplication of the entire transition or just some of the transition properties by including different values for transition properties in the global (versus the hover-only) state.

When declaring transitions in multiple states, the transition included is to that state:

a {

background: yellow;

transition: 200ms background-color linear 0s;

}

a:hover {

background-color: orange;

/* delay when going <strong>to</strong> the :hover state */

transition-delay: 50ms;

}

In this scenario, when the user hovers over a link, the background color waits 50 milliseconds before transitioning to orange. When the user mouses off the link, the background starts transitioning back to yellow immediately. In both directions, the transition takes 200 milliseconds to complete, and the gradual change proceeds in a linear manner. The 50 milliseconds is included in the :hover (orange) state. The delay happens, therefore, as the background changes to orange. https://www.safaribooksonline.com/library/view/css-the-definitive/9781449325053/assets/play-icon-round

In our drop-down menu example, on :hover, the menu appears and grows over 200 milliseconds, easing in after a delay of 50 milliseconds. The transition is set with the transition property in the default (nonhovered) state. When the user mouses out, the properties revert over 200 milliseconds, easing out after a delay of 50 milliseconds. This reverse effect is responding to the transition value from the non-hovered state. This is the default behavior, but it’s something we can control. The best user experience is this default behavior, so you likely don’t want to alter it—but it’s important to know that you can.

If we want the closing of the menu to be jumpy and slow (we don’t want to do that; it’s bad user experience. But for the sake of this example, let’s pretend we do), we can declare two different transitions:

nav ul ul {

transform: scale(1, 0);

opacity: 0;

...

transition: all 4s steps(8, start) 1s;

}

nav li:hover ul {

transform: scale(1, 1);

opacity: 1;

transition: all 200ms linear 50ms;

}

Transitions are to the to state: when there’s a style change, the transition properties used to make the transition are the new values of the transition properties, not the old ones. We put the smooth, linear animation in the :hover state. The transition that applies is the one we are going toward. In the preceding example, when the user hovers over the drop-down menu’s parent li, the opening of the drop-down menu will be gradual but quick, lasting 200 milliseconds after a delay of 50 milliseconds. When the user mouses off the drop-down menu or its parent li, the transition will wait one second and take four seconds to complete, showing eight steps along the way.

When we only have one transition, we put it in the global from state, as you want the transition to occur toward any state, be that a hovering or a class change. Because we want the transition to occur with any change, we generally put the only transition declaration in the initial, default (least specific) block. If you do want to exert more control and provide for different effects depending on the direction of the transition, make sure to include a transition declaration in all of the possible class and UI states.

Warning

Beware of having transitions on both ancestors and descendants. Transitioning properties soon after making a change that transition ancestral or descendant nodes can have unexpected outcomes. If the transition on the descendant completes before the transition on the ancestor, the descendant will then resume inheriting the (still transitioning) value from its parent. This effect may not be what you expected.

Animatable Properties and Values

Before implementing transitions and animations, it is important to understand what properties are transitionable and animatable. You can transition (or animate) any animatable CSS properties; but which properties are animatable?

While we’ve included a list of these properties in “Animatable Properties”, CSS is evolving, and the animatable properties list will likely get new additions.6

Interpolation is the construction of data points between the values of known data points. The key guideline to determining if a property value is animatable is whether the computed value can be interpolated. If a property’s keywords are computed values, they can’t be interpolated; if its keywords compute to a number, they can be. The quick rule of thought is that if you can determine a midpoint between two property values, those property values are probably animatable. Values that are interpolatable are animatable. Those that aren’t, aren’t.

For example, the display values are nonnumeric keywords. Values like block and inline-block aren’t numeric and therefore don’t have a midpoint; they aren’t animatable. The transform property values of rotate(10deg) and rotate(20deg) have a midpoint of rotate(15deg); they are animatable.

The border property is shorthand for border-style, border-width, and border-color (which, in turn, are themselves shorthand properties for the four side values). While there is no midpoint between any of the border-style values, the border-width property length units are numeric, so they can be animated. The keyword values of medium, thick, and thin have numeric equivalents and are interpolatable: the computed value of the border-width property computes those keywords to lengths.

In the border-color value, colors are numeric—the named colors all represent hexadecimal color values—so colors are animatable as well. If you transition from border: red solid 3px to border: blue dashed 10px, the border width and border colors will transition at the defined speed, but border-style will jump from solid to dashed as soon as the new value is applied.

transitionend events will occur for all the animatable properties. In this case, there will be eight transitionend events, for border-top-width, border-right-width, border-bottom-width, border-left-width, border-top-color, border-right-color, border-bottom-color, and border-left-color.

As noted (see Table 10-3), numeric values tend to be animatable. Keyword values that aren’t translatable to numeric values generally aren’t. CSS functions that take numeric values as parameters generally are animatable. One exception to this rule of thought is visibility: while there is no midpoint between the values of visible and hidden, visibility values are interpolatable between visible and not-visible. When it comes to the visibility property, either the initial value or the destination value must be visible or no interpolation can happen. The value will change at the end of the transition from visible to hidden. For a transition from hidden to visible, it changes at the start of the transition.

auto should generally be considered a nonanimatable value and should be avoided for animations and transitions. According to the specification, it is not an animatable value, but some browsers interpolate the current numeric value of auto (such as height: auto) to be 0px. auto is nonanimatable because it is a computed value for properties like height, width, top, bottom, left, right, and margin.

Often an alternative property or value may work. For example, instead of changing height: 0 to height: auto, use max-height: 0 to max-height: 100vh, which will generally create the expected effect. The auto value is animatable for min-height and min-width, since min-height: auto actually computes to 0.

How Property Values Are Interpolated

Interpolation can happen when values falling between two or more known values can be determined. Interpolatable values can be transitioned and animated.

Numbers are interpolated as floating-point numbers. Integers are interpolated as whole numbers, incrementing or decrementing as whole numbers.

In CSS, length and percentage units are translated into real numbers. When transitioning or animating calc(), or from one type of length to or from a percentage, the values will be converted into a calc() function and interpolated as real numbers.

Colors, whether they are HSLA, RGB, or named colors, are interpolated into their RGBA equivalent values for transitioning.

When animating font weights, if you use keyterms like bold, they’ll be converted to numeric values and animated in steps of multiples of 100. You may be used to writing bold and normal, but the values of 100 through 900 have been around as long as CSS—since CSS Level 1 in 1996.

When including animatable property values that have more than one component, each component is interpolated appropriately for that component. For example, text-shadow has up to four components: the color, x, y, and blur. The color is interpolated as color: the x, y, and blur components are interpolated as lengths. Box shadows have two additional optional properties: inset (or lack thereof) and spread. spread, being a length, is interpolated as such. The inset keyterm cannot be converted to a numeric equivalent: you can transition from one inset shadow to another inset shadow, or from one drop shadow to another drop shadow multi-component value, but there is no way to gradually transition between inset and drop shadows.

Similar to values with more than one component, gradients can be transitioned only if you are transitioning gradients of the same type (linear or radial) with equal numbers of color stops. The colors of each color stop are then interpolated as colors, and the position of each color stop is interpolated as length and percentage units.

Repeating values

When you have simple lists of other types of properties, each item in the list is interpolated appropriately for that type—as long as the lists have the same number of items or repeatable items, and each pair of values can be interpolated:

.img {

background-image:

url(1.gif), url(2.gif), url(3.gif), url(4.gif),

url(5.gif), url(6.gif), url(7.gif), url(8.gif),

url(9.gif), url(10.gif), url(11.gif), url(12.gif);

background-size: 10px 10px, 20px 20px, 30px 30px, 40px 40px;

transition: background-size 1s ease-in 0s;

}

.img:hover {

background-size: 25px 25px, 50px 50px, 75px 75px, 100px 100px;

}

For example, in transitioning four background-sizes, with all the sizes in both lists listed in pixels, the third background-size from the pretransitioned state can gradually transition to the third background-size of the transitioned list. In the preceding example, background images 1, 6, and 10 will transition from 10px to 25px in height and width when hovered. Similarly, images 3, 7, and 11 will transition from 30px to 75px, and so forth.

Remember, when there aren’t enough declarations to match the number of background layers, the values are repeated. If there are too many values, the excess values are ignored. In this case, the background-size values are repeated three times, as if the CSS had been written as:

.img {

...

background-size: 10px 10px, 20px 20px, 30px 30px, 40px 40px,

10px 10px, 20px 20px, 30px 30px, 40px 40px,

10px 10px, 20px 20px, 30px 30px, 40px 40px;

...

}

.img:hover {

background-size: 25px 25px, 50px 50px, 75px 75px, 100px 100px,

25px 25px, 50px 50px, 75px 75px, 100px 100px,

25px 25px, 50px 50px, 75px 75px, 100px 100px;

}

If a property doesn’t have enough comma-separated values to match the number of background images, the list of values is repeated until there are enough, even when the list in the :hover state doesn’t match the initial state:

.img:hover {

background-size: 33px 33px, 66px 66px, 99px 99px;

}

If we transitioned from four background-size declarations in the initial state to three background-size declarations in the :hover state, all in pixels, still with 12 background images, the hover and initial state values are repeated (three and four times respectively) until we have the 12 necessary values, as if the following had been declared:

.img {

...

background-size: 10px 10px, 20px 20px, 30px 30px,

40px 40px, 10px 10px, 20px 20px,

30px 30px, 40px 40px, 10px 10px,

20px 20px, 30px 30px, 40px 40px;

...

}

.img:hover {

background-size: 33px 33px, 66px 66px, 99px 99px,

33px 33px, 66px 66px, 99px 99px,

33px 33px, 66px 66px, 99px 99px,

33px 33px, 66px 66px, 99px 99px;

}

If a pair of values cannot be interpolated—for example, if the background-size changes from contain in the default state to cover when hovered—then, according to the specification, the lists are not interpolatable. However, some browsers ignore that particular pair of values for the purposes of the transition, but still animate the interpolatable values.

There are some property values that can animate if the browser can infer implicit values. For example, shadows. For shadows, the browser will infer an implicit shadow box-shadow: transparent 0 0 0 or box-shadow: inset transparent 0 0 0, replacing any values not explicitly included in the pre- or post-transition state. These examples are in the chapter files for this book.

Only the interpolatable values lead to transitionend events.

As noted previously, visibility animates differently than other properties: if animating or transitioning to or from visible, it is interpolated as a discrete step. It is always visible during the transition or animation as long as the timing function output is between 0 and 1. It will switch at the beginning if the transition is from hidden to visible. It will switch at the end if the transition is from visible to hidden. Note that this can be controlled with the step timing functions.

Animatable Properties

There is a list of animatable properties in the CSS Transitions specification. This list only lists the CSS 2.1 properties that are transitionable, so it is not wholly accurate.

Table 10-3 shows a list of animatable properties and how their values are interpolated.

Table 10-3. Animatable properties

Property name

Interpolation

color

color

as color

opacity

as number

columns

column-width

as length

column-count

as integer

column-gap

as length

column-rule (see longhands)

column-rule-color:

as color

column-rule-style:

no

column-rule-width:

as length

break-before

no

break-after

no

break-inside

no

column-span

no

column-fill

no

text

hyphens

no

letter-spacing

as length

word-wrap

no

overflow-wrap

no

text-transform

no

tab-size

as length

text-align

no

text-align-last

no

text-indent

as length, percentage, or calc();

direction

no

white-space

no

word-break

no

word-spacing

as length

line-break

no

Text decorations

text-decoration-color:

as color

text-decoration-style:

no

text-decoration-line:

no

text-decoration-skip

no

text-shadow

as shadow list

text-underline-position

no

Flexible boxes

align-content

no

align-items

no

align-self

no

flex-basis

as length, percentage, or calc();

flex-direction

no

flex-flow

no

flex (see longhand)

flex-grow

as number

flex-shrink

as number

flex-basis:

as length, percentage, or calc();

flex-wrap

no

justify-content

no

order

as integer

Background and borders

background

background-color:

as color

background-image:

no

background-clip:

no

background-position:

as list of length, percentage, or calc

background-size:

as list of length, percentage, or calc

background-repeat:

no

background-attachment

no

abackground-origin

no

Borders

border (see longhand)

border-color

as color

border-style

no

border-width

as length

border-radius

as length, percentage, or calc();

border-image

no (see longhand)

border-image-outset

no

border-image-repeat

no

border-image-slice

no

border-image-source

no

border-image-width

no

Box model

box-decoration-break

no

box-shadow

as shadow list

margin

as length

padding

as length

box-sizing

no

max-height

as length, percentage, or calc();

min-height

as length, percentage, or calc();

height

as length, percentage, or calc();

max-width

as length, percentage, or calc();

min-width

as length, percentage, or calc();

width

as length, percentage, or calc();

overflow

no

visibility

as visibility (see “How Property Values Are Interpolated”)

Table

border-collapse

no

border-spacing

no

caption-side

no

empty-cells

no

table-layout

no

vertical-align

as length

Positioning

bottom

as length, percentage, or calc();

left

as length, percentage, or calc();

right

as length, percentage, or calc();

top

as length, percentage, or calc();

float

no

clear

no

position

no

z-index

as integer

Fonts

font (see longhand)

font-style

no

font-variant

no

font-weight

as font weight

font-stretch

as font stretch

font-size

as length

line-height

as number, length

font-family

no

font-variant-ligatures

no

font-feature-settings

no

font-language-override

no

font-size-adjust

as number

font-synthesis

no

font-kerning

no

font-variant-position

no

font-variant-caps

no

font-variant-numeric

no

font-variant-east-asian

no

font-variant-alternates

no

Images

object-fit

no

object-position

as length, percentage, or calc();

image-rendering

no

image-orientation

no

Counters, lists, and generated content

content

no

quotes

no

counter-increment

no

counter-reset

no

list-style

no

list-style-image

no

list-style-position

no

list-style-type

no

Page

orphans

no

page-break-after

no

page-break-before

no

page-break-inside

no

widows

no

User interface

outline (see longhand)

outline-color

as color

outline-width

as length

outline-style

no

outline-offset

as length

cursor

no

resize

no

text-overflow

no

Animations

animation

no (see longhands)

animation-delay

no

animation-direction

no

animation-duration

no

animation-fill-mode

no

animation-iteration-count

no

animation-name

no

animation-play-state

no

animation-timing-function

no, though animation-timing-function can be included in keyframes

Transitions

transition

no (see longhands)

transition-delay

no

transition-duration

no

transition-property

no

transition-timing-function

no

Transform properties

transform

as transform (see Transforms in CSS [O’Reilly])

transform-origin

as length, percentage or calc();

transform-style

no

perspective

as length

perspective-origin

as simple list of a length, percentage or calc();

backface-visibility

no

Compositing and blending

background-blend-mode

no

mix-blend-mode

no

isolation

no

Shapes

shape-outside

yes, as basic-shape

shape-margin

as length, percentage, or calc();

shape-image-threshold

as number

Miscellaneous

clip (deprecated)

as rectangle

display

no

unicode-bidi

no

text-orientation

no

ime-mode

no

all

as each of the properties of the shorthand (all properties but unicode-bidi and direction)

will-change

no

box-decoration-break

no

touch-action

no

initial-letter

no

initial-letter-align

no

transition Events Revisited

The transitionend event fires when a transition completes.

The transitionend event has three properties. propertyName is the name of the CSS property whose transition completed, and elapsedTime is the number of seconds the transition had been running at the time the event fired, not including the transition delay. The third property is the pseudoElement property, which returns the name of the pseudo-element on which the transition occurred, either ::before or ::after (with two colons), or the empty string if the transition occurred on an element and not a pseudo-element.

Currently, the transitionend event will only occur if there is a positive, nonzero transition-delay or a nonzero transition-duration. The transitionend event only occurs if the sum of transition-delay (which can be negative) and transition-duration (which can’t) is greater than zero. If there is no delay or no duration, there is no gradual transition, and no transitionend is fired, even if a property value has changed state.

You can use the addEventListener() method for the transitionend event to listen for this event.

If a keyframe animation, explained in the next chapter, is applied to a transitioning element, and it animates the same properties that are being transitioned, the animation takes precedence over the transition. Oddly, the transitionendevent will still occur, but it will occur at the end of the animation.

Printing transitions

When web pages or web applications are printed, the stylesheet for print media is used. If your style element’s media attribute matches only screen, the CSS will not impact the printed page at all.

Often, no media attribute is included; it is as if media="all" were set, which is the default. Depending on the browser, when a transitioned element is printed, either the interpolating values are ignored, or the property values in their current state are printed.

You can’t see the element transitioning on a piece of paper, but in some browsers, like Chrome, if an element transitioned from one state to another, the current state at the time the print function is called will be the value on the printed page, if that property is printable. For example, if a background color changed, neither the pre-transition or the post-transition background color will be printed, as background colors are generally not printed. However, if the text color mutated from one value to another, the current value of color will be what gets printed on a color printer or PDF.

In other browsers, like Firefox, whether the pre-transition or post-transition value is printed depends on how the transition was initiated. If it initiated with a hover, the non-hovered value will be printed, as you are no longer hovering over the element while you interact with the print dialog. If it transitioned with a class addition, the post-transition value will be printed, even if the transition hasn’t completed. The printing acts as if the transition properties are ignored.

Given that there are separate printstyle sheets or @media rules for print, browsers compute style separately. In the print style, styles don’t change, so there just aren’t any transitions. The printing acts as if the property values changed instantly, instead of transitioning over time.

1 Changing a background image may take longer than 16 milliseconds to decode and repaint to the page. This isn’t a transition; it is just poor performance.

2 There is a pending resolution in the CSS Working Group stating that nonanimatable properties should obey transitions. This will likely not be web-compatible and will probably be reverted.

3 All of the examples in this chapter can be found at http://standardista.com/transitions.

4 This might change. The CSS Working Group is considering making all property values animatable, switching from one value to the next at the midpoint of the timing function if there is no midpoint between the pre and post values.

5 You can test the various transition-timing-function examples at http://www.standardista.com/css3/transitions.

6 A proposed change to the specifications would make all properties transitionable, even if they aren’t in fact animatable. This has yet to be added to the specification, and I don’t foresee it being implemented.