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 transition
property, 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 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 3, 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 2-1, we are transitioning the submenu’s height and
opacity over 200 milliseconds. 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.
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.
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.
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 2-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 2-1. The style change in this scenario is caused by hovering over navigational elements.
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 2-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
:
/* 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 2-2, matches some of the time, when the input has focus, whether the
input is matching the :valid
or :invalid
selector 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.
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. 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 ul
condition—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:
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.
transition-property
PropertyThe 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.”
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.
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.
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
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 3.
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.
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 all
declaration. 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:
document
.querySelector
(
'div'
)
.addEventListener
(
'transitionend'
,
function
(
e
)
{
console
.
log
(
e
.
propertyName
);
}
);
The transitionend
event includes three event specific attributes: 1) property
Name
, 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.
transition-duration
PropertyThe 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.
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.
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
;
...
}
transition-timing-function
PropertyDo 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.
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 3.
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 2-1.
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 2-1 and displayed in Figure 2-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.
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 2-4, the website easings.net provides many additional cubic Bézier function values you can use to provide for a more realistic, delightful animation.
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 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
There are also step timing functions available, with two predefined step values:
Timing function | Definition |
---|---|
|
Stays on the final keyframe throughout transition. Equal to |
|
Stays on the initial keyframe throughout transition. Equal to |
|
Displays n stillshots, where the first stillshot is n/100 percent of the way through the transition. |
|
Displays n stillshots, staying on the initial values for the first n/100 percent of the time. |
As Figure 2-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.
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”.
transition-delay
PropertyThe 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-property
been applied) until the property values declared in the transition
or
transition-property
value begins animating to the next value.
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-delay
s are described in “Negative values”.
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 2-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.
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:
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.
If you hover over the navigation item from Figure 2-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.
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 2-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.
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 2-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 div
will 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 , 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.
transition
Shorthand PropertyThe transition
shorthand property combines the four properties just
described—transition-property
, transition-duration
,
transition-timing-function
, and transition-delay
—into a single
property.
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
;
...
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 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.
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.
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.
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 2-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.
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.
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.
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 2-3 shows a list of animatable properties and how their values are interpolated.
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 RevisitedThe 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 transitionend
event
will still occur, but it will occur at the end of the animation.
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.