People of the world, strap yourself in and hold on tight, for you are
about to experience truly hair-raising excitement as you get to grips
with the intricacies of the hugely interesting CSS timing function!
OK, so the subject matter of this article probably hasn’t sent your blood racing, but all jokes aside,
the timing function is a bit of a hidden gem when it comes to CSS animation, and you could well be surprised by just how much you can do with it.
First of all, let’s set the scene and ensure we’re all familiar with
the scenarios in which the timing function is relevant. As alluded to,
the functionality becomes available when you’re working in the realm of
CSS animation, which includes CSS transitions as well as keyframe-based
animation. So, what exactly is it and what does it do?
The CSS Timing Function Explained
It’s one of the less obvious animation-based CSS properties, whereas
most of its counterparts are rather self-explanatory. Nevertheless, the
gist of it is that it enables you to control and vary the acceleration
of an animation — that is, it defines where the animation speeds up and
slows down over the specified duration.
While it does not affect the actual duration of an animation, it could have profound effects on how fast or slow the user
perceives
the animation to be. If you’re not sold having learned of its actual
purpose, then stick with me here because the timing-function property
gets a lot more interesting than the definition suggests.
Note: There is no actual property named “timing-function.” When I refer to this “property,” I am actually referring to both the
transition-timing-function
and the
animation-timing-function
properties.
Before moving on, let’s just familiarize ourselves with the syntax
and where it fits into the whole process of defining an animation in
CSS. To keep things simple, let’s use a CSS transition for this example.
We’ll begin with the full array of transition properties:
div {
transition-property: background;
transition-duration: 1s;
transition-delay: .5s;
transition-timing-function: linear;
}
div {
transition: background 1s .5s linear;
}
The shorthand for defining a transition is fairly lenient, the only
requirement for the order being that the delay parameter must be stated
after the duration value (but not necessarily immediately after).
Furthermore, the
transition-duration
value is the only one
that is actually required for the code to function; and because the
default values of the other parameters are adequate most of the time,
your transitions seldom need to be anything more than the following
snippet:
div {
transition: 1s;
}
div {
transition: all 1s 0s ease;
}
But that’s a bit boring. While the defaults are often sufficient for
standard hover events and such, if you’re working on something a little
more substantial, then the timing function is a serious trick for
fine-tuning an animation!
Anyway, you now have an idea of what the timing function does. Let’s look at how it does it.
Looking Under the Hood
Many of you probably don’t look past the available keywords for the timing-function property, of which there are five:
ease
(the default),
ease-in
,
ease-out
,
ease-in-out
and
linear
. However, these keywords are simply shorthands for defining the Bézier curve.
The what?!
You might not be familiar with the term, but I’d wager that you’ve
actually seen a Bézier curve — heck, if you’ve used graphical editing
software, then you’ve probably even created one! That’s right, when you
use the Pen or Path tool to create a nice smooth curve, then you’re
drawing a Bézier curve! Anyway, to get to the point of this section,
the Bézier curve is the magic behind the timing function; it basically describes the acceleration pattern on a graph.
This Bézier curve translates to the ease
keyword.
If you’re anything like me the first time I saw a Bézier curve like
this, then you might be wondering how on earth that curve could be
formed from four points plotted on a graph! I couldn’t possibly tell you
in words, but, fortunately, I have a particularly fantastic GIF to do
the job for me,
courtesy of Wikipedia.
A cubic Bézier curve being drawn (Image: Wikipedia)
Because this curve is formed from four points, we refer to it as a
“cubic” Bézier curve, as opposed to a “quadratic” curve (three points)
or a “quartic” curve (five points).
Introducing The cubic-bezier() Function
Now then, this is where things get really exciting, as I reveal that you can actually get access to this curve through the
cubic-bezier()
function, which can simply be used in place of the keywords of the
timing-function property value. I appreciate that you may need a moment
to contain your excitement…
With the
cubic-bezier()
function, you can
manipulate the Bézier curve whichever way you desire,
creating completely custom acceleration patterns for your animation!
So, let’s look at how this function works and how it enables you to
create your very own Bézier curve.
First, we know that the curve is formed by four points, which are
referred to as point 0, point 1, point 2 and point 3. The other
important thing to note is that the first and last points (0 and 3) are
already defined on the graph, with point 0 always sitting at
0,0
(bottom left) and point 3 always sitting at
1,1
(top right).
That leaves just point 1 and point 2 available for you to plot on the graph, which you can do using the
cubic-bezier()
function! The function takes four parameters, the first two being the x
and y coordinates of point 1 and the latter two being the x and y
coordinates of point 2.
transition-timing-function: cubic-bezier(x, y, x, y);
To get comfortable with the syntax, as well as with how it creates
the curve and with its physical effect on the animation, I’ll take you
through the five timing-function keywords, their equivalent
cubic-bezier()
values and the resulting effect on the animation.
ease-in-out
Let’s start with the
ease-in-out
keyword because the logic behind this curve and how it translates to the animation are probably the easiest to understand.
transition-timing-function: cubic-bezier(.42, 0, .58, 1);
A perfectly symmetrical Bézier curve, which means that the
animation will ease in to full speed and then ease out at exactly the
same rate.
You can see that point 1 is positioned at 0.42 along the x-axis and
at 0 on the y-axis, whereas point 2 is positioned at 0.58 on the x-axis
and 1 on the y-axis. The result is a perfectly symmetrical Bézier curve,
which means that the animation will ease in to full speed and then ease
out at exactly the same rate; hence, the name of this keyword.
If you look at the demo below, you will see the physical effect of the
ease-in-out
value, as well as how it compares to the other keyword values.
Ease
The
ease
keyword is the default value of the CSS
timing-function property, and it is actually quite similar to the
previous one, although it eases in at a faster rate before easing out
much more gradually.
transition-timing-function: cubic-bezier(.25, .1, .25, 1);
The curve for the keyword ease
eases in at a faster pace before easing out much more gradually.
You can see a sharper incline in the curve initially, while the
finish is more drawn out, which directly translates to the physical
effect of this timing function on the animation. Don’t forget to refer
to the earlier demo after reviewing each of these examples to compare
the effects.
ease-in and ease-out
The
ease-in
and
ease-out
keywords are,
unsurprisingly, exact opposites. The former eases in before maintaining
full speed right through to the finish, while the latter hits full speed
right off the bat before easing to a finish. The
ease-in-out
keyword that we looked at previously is, as logic would suggest, a perfect combination of these two Bézier curves.
transition-timing-function: cubic-bezier(.42, 0, 1, 1);
transition-timing-function: cubic-bezier(0, 0, .58, 1);
Bézier curve for the ease-in
keyword (left) and the ease-out
keyword (right) (Large version)
Linear
The final keyword to address does not correspond to a curve at all. As the name implies, the
linear
timing-function value maintains the same speed throughout the
animation, which means that the resulting Bézier curve (or lack of)
would be just a straight line. There is simply no
varying acceleration pattern to depict on the graph.
transition-timing-function: cubic-bezier(0, 0, 1, 1);
The linear
timing-function value maintains the same speed throughout the animation.
If you refer back to the demo, you will probably notice that, despite
all of the examples having the exact same duration values, some of the
animations appear to be slower than the others. Why is that? Well, to
take the
ease-in-out
keyword as an example, we know that it
starts and finishes at a slower pace, which means that it has to cover
the middle ground of the animation at a much faster pace. This
effectively ensures that we perceive the actual animation to be both
shorter and faster, while, say, the linear animation appears longer and
more drawn out.
You might feel that this article is very slowly easing into its full
pace (see what I did there?), so now it’s finally time to step things up
a bit and look at how to use the
cubic-bezier()
function to create our own custom timing functions.
Creating Custom Acceleration Patterns With The cubic-bezier() Function
Now that we’ve seen how the keywords equate to the corresponding
Bézier curves and seen their effect on the animation, let’s look at how
to manipulate the curve to create custom acceleration patterns.
You should now be able to plot points 1 and 2 on the graph using the
cubic-bezier()
function and have a pretty solid idea of how this will affect the
animation. However, considering that you’re plotting points on a graph
that you typically can’t see, obviously this could get very tedious very
quickly.
Fortunately, people like Lea Verou exist, who seemingly won’t rest
until CSS development couldn’t be made any easier! Lea has developed the
aptly named Cubic Bézier, which is basically a playground for creating
completely custom Bézier curves and comparing them in action to the
predefined keywords. What this means for you is that, instead of
tediously editing the decimals in your
cubic-bezier()
functions, you can just visit
Cubic Bezier and play around with the curve until you’ve achieved the desired effect. Much more convenient.
Lea Verou’s superbly useful Cubic Bézier (Large version)
The shorthand keywords give you great options for timing functions to
start with, but the differences between them often appear minor. Only
when you start to create custom Bézier curves will you realize the
radical effect the timing function can have on an animation.
Just look at the following working examples to see the extreme
differences between the animations, despite all of them having the exact
same duration values.
Let’s take a closer look at the first of these examples and try to understand why it produces such a radically different effect.
transition-timing-function: cubic-bezier(.1, .9, .9, .1);
Example of a custom Bézier curve
The main difference between this timing function and the default
keywords is the sharp inclines of the Bézier curve against the
“progression” scale (the y-axis). This means that the animation
progresses in short bursts, with a long gradual pause in the middle
(where the curve levels out). This pattern contrasts starkly with what
we’ve become used to with the timing-function keywords, which take the
opposite approach, with the easing periods coming at the beginning or
the end of the animation, rather than in the middle.
With custom Bézier curves now in the bag, surely we have all but exhausted the capabilities of the
cubic-bezier()
function, right?! You’d think so, but this crafty beggar has got even more up its sleeve!
Getting Creative With Bézier Curves
Yep, it’s true: Bézier curves get even more exciting! Who’d have
thought? The scope widens with the revelation that only the time scale
(x-axis) is limited to the 0–1 range on the graph, whereas the
progression scale (y-axis) can extend below and beyond the typical 0–1
range.
The progression scale is exactly what you’d think it is, with the
bottom end (0) marking the beginning of the animation and the top end
(1) marking the end of the animation. Typically, the cubic Bézier curve
always travels north along this progression scale at varying intensities
until it reaches the end point of the animation. However, the ability
to plot points 1 and 2 outside of this 0–1 range allows the curve to
meander back
down the progression scale, which actually causes
reverse motion in the animation! As ever, the best way to understand
this is through visuals:
A custom Bézier curve using a value outside of the typical 0–1 range
You can see that point 2 is plotted outside of the typical 0–1 range
at -0.5, which in turn pulls the curve downward. If you look at the
following demo, you’ll see that this creates a bouncing effect in the
middle of the animation.
Conversely, you could place this backward motion at the beginning of
the animation and also make it temporarily run past its intended
finishing point. Think of it like taking a couple of steps back to get a
running start; then, at the end, your momentum carries you past your
destination, causing you to take a couple of steps back to ensure that
you arrive at the intended destination. Look at the working example to
really understand what we’re talking about here. In addition, the Bézier
curve that produces this effect can be seen below.
A custom Bézier curve using a value outside of the typical 0–1 range
You should now have a pretty good idea of how
cubic-bezier()
values outside of the typical 0–1 range can physically affect how an
animation plays out. We can look at moving boxes all day long, but let’s
finish this section with an example that clearly demonstrates this type
of creative timing function.
That’s right: We’re animating a floating balloon! …What? Haven’t you always wanted to do that with CSS?
The brief for this animation is to “add helium” to the balloon on
click so that it floats to the “ceiling,” where it will bounce slightly
before sticking to the top, as it naturally would. Using a
cubic-bezier()
value outside of the 0–1 range allows us to create the bounce and
ultimately helps to produce a realistic effect. The following snippet
shows the coordinates used in the
cubic-bezier()
function, and the resulting Bézier curve appears below that.
transition-timing-function: cubic-bezier(.65, 1.95, .03, .32);
A custom Bézier curve to emulate a bouncing balloon
This example explains particularly well how the curve translates to
the resulting animation because it reflects it almost perfectly. First,
you can see that the curve travels from the beginning of the progression
scale to the end in a straight line, indicating that the balloon
travels from the start of the animation to the finish at a constant
speed. Then, very similarly to the balloon, the curve bounces off the
tip of the scale and goes into reverse before returning steadily and
gradually to the top. All quite straightforward really!
Once you’ve mastered the curve and the art of manipulating it to do what you want, you’ve nailed it.
Timing Functions And Keyframe-Based CSS Animation
One final thing to note before moving on (yes, there is more to
cover!) is how timing functions behave when applied to CSS keyframe
animation. The concepts are all exactly the same as those in the
transition examples we’ve been working with so far, but with one
exception that is important to be aware of: When you apply a timing
function to a set of keyframes, it is executed
per keyframe, rather than for the animation as a whole.
To clarify, if you have four keyframes that move a box around four
corners of a square, and you apply the “bouncing” timing function that
we used in the earlier balloon example, then
each of the four
movements would experience the bounce, rather than the entire animation.
Let’s see this in action and view the code.
@keyframes square {
25% {
top:200px;
left:0;
}
50% {
top:200px;
left:400px;
}
75% {
top:0;
left:400px;
}
}
div {
animation: square 8s infinite cubic-bezier(.65, 1.95, .03, .32);
top: 0;
left: 0;
}
Note that if the
100%
keyframe isn’t defined, then the
element will simply return to its original style, which is the desired
result in this case, so defining it is not necessary. It is clearly
evident from the demo that the timing function is applied to each of the
four keyframes because they each appear to bounce off the walls of the
container.
If you need certain keyframes to take on a timing function that is
different from the others, then go ahead and apply a separate
timing-function value directly to the keyframe, as indicated in the
following snippet.
@keyframes square {
50% {
top: 200px;
left: 400px;
animation-timing-function: ease-in-out;
}
}
Introducing The steps() Timing Function
Did you think we were done with timing functions? Ha, think again,
pal! I told you that there’s a lot more to CSS timing functions than a
few predefined easing functions!
For this section, we can swap our curves for straight lines as we
explore the concept of “stepping functions,” which are achieved through
the
steps()
timing function.
The
steps()
function is more of a niche tool, but it’s
useful to have in the toolkit nonetheless. It enables you to break up an
animation into steps, rather than the usual tweened motion that we’re
used to. For example, if we wanted to animate a square moving 400 pixels
to the right in four steps over 4 seconds, then the square would jump
100 pixels to the right every second, rather than animating in a
continuous motion. Let’s examine the syntax that we’d need for this
particular example, which should be an absolute breeze now that we’ve
tackled the intricacies of the
cubic-bezier()
function!
div {
transition: 4s steps(4);
}
div:target {
left: 400px;
}
As you can see, it’s a simple matter of stating the number of steps
to divide the animation into — but bear in mind that this number must be
a positive integer, so no negatives or decimals. However, a second,
optional parameter affords us slightly more control, the possible values
for which are
start
and
end
, the latter being the default value.
transition-timing-function: steps(4, start);
transition-timing-function: steps(4, end);
A value of
start
would run the animation at the beginning of each step, whereas a value of
end
would run the animation at the end of each step. Using the previous
“moving box” example, the following image does a far better job of
explaining the difference between these values.
The difference between the start and end values in the steps()
function.
You can see that with a value of
start
, the animation begins as soon as it is triggered, whereas with a value of
end
, it begins at the end of the first step (in this case, one second after being triggered).
And just to ensure that this overview is truly comprehensive, there are also two predefined keywords for the
steps()
function:
step-start
and
step-end
. The former is equivalent to
steps(1, start)
, and the latter is the same as
steps(1, end)
.
Creative Use Cases For Stepping Functions
OK, so you probably don’t have much of a need to animate a moving box
in steps very often, but there are plenty of other cool uses for the
steps()
function. For example, if you have all of the sprites for a basic
cartoon, then you could use this technique to play it through frame by
frame, using just a couple of CSS properties! Let’s look at a demo and
the code that makes it function.
div {
width: 125px;
height: 150px;
background: url(images/sprite.jpg) left;
transition: 2s steps(16);
}
div:target {
background-position: -2000px 0;
}
First, we have a small rectangular box (125 pixels wide), which has a
background image (2000 pixels wide) containing 16 frames side by side.
This background image is initially flush with the left edge of the box;
so, all we need to do now is move the background image all the way to
the left so that all 16 frames pass through the small rectangular
window. With a normal animation, the frames would just slide in and out
of view as the background image moves leftwards; however, with the
steps()
function, the background image can move to the left in 16 steps,
ensuring that each of the 16 frames snaps in and out of view, as
desired. And just like that, you are playing a basic cartoon using just a
CSS transition!
This GIF demonstrates the concept of the background image passing
through the “window” in steps, so that each frame snaps in and out of
view. (Large version)
Another creative use of the
steps()
function that I’ve
found comes courtesy of Lea Verou (who else?), who has come up with a
particularly clever typing animation, which I’ll break down for you now.
First, you need some text, and — unfortunately — you need to know
exactly how many characters you’re working with because you’ll need to
use this number in the CSS. Another requirement is that the font must be
monospaced, so that all characters are exactly the same width.
<p>smashingmag</p>
.text {
width: 6.6em;
width: 11ch;
border-right: .1em solid;
font: 5em monospace;
}
The text we’re working with has 11 characters. With the help of the
ch
CSS unit, we can actually use this figure to define the width of the
paragraph, although we should specify a fallback width for browsers that
don’t support this unit. The paragraph is then given a solid black
border on the right side, which will become the cursor. Now everything
is in place; we just need to animate it, which is extremely simple.
Two separate animations are required: one for the cursor and one for
the typing. To achieve the former, all we need to do is make the black
border blink, which couldn’t be simpler.
@keyframes cursor {
50% {
border-color: transparent;
}
}
.text {
animation: cursor 1s step-end infinite;
}
As intended, the black border simply switches between black and transparent and then loops continuously. This is where the
steps()
function is vital because, without it, the cursor would just fade in and out, rather than blink.
Finally, the typing animation is just as simple. All we need to do is
reduce the width of the paragraph to zero, before animating it back to
full width in 11 steps (the number of characters).
@keyframes typing {
from {
width: 0;
}
}
.text {
animation: typing 8s steps(11),
cursor 1s step-end infinite;
}
With this single keyframe in place, the text will reveal itself one letter at a time over 8 seconds, while the black
border-right
(the cursor) will blink away continuously. The technique is very simple yet extremely effective.
Just to add to this excellent use of the
steps()
function by Lea Verou, reversing the effect and making the text appear
to be deleted is also a cinch. To achieve this, just change the keyframe
keyword so that it reads
to
rather than
from
, and then add an
animation-fill-mode
parameter of
forwards
to the set of
animation
rules. This will ensure that once the text “deletes” (i.e. when the
animation has finished), the text will remain deleted. Look at the demo
below to see this in action.
The downside to both of the examples featured in this section is that
you must know the number of frames or characters beforehand in order to
specify the right number of steps, and if this number changes at all,
then you will need to alter the code as well. Still, the
steps()
function has shown its worth here and is another fantastic piece of functionality of the CSS timing function.
The Browser Support Situation
We’ve established that you can’t use a CSS timing function unless the
browser supports CSS-based animation — namely, the CSS Transitions and
CSS Animation (keyframe-based) modules. Fortunately, support is in
pretty great shape these days.
Support for CSS Transitions
Browser |
Prefixed support |
Unprefixed support |
Internet Explorer |
N/A |
10+ |
Firefox |
4+ (-moz- ) |
16+ |
Chrome |
4+ (-webkit- ) |
26+ |
Safari |
3.1+ (-webkit- ) |
6.1+ |
Opera |
10.5+ (-o- prefix) |
12.1+ |
Although all current browser versions have dropped the prefix for
transitions (which is awesome), you’d be wise to still include the
-webkit-
prefix to cater to old mobile browsers. I think the need to include the
-moz-
and
-o-
prefixes, however, has passed, as long as you progressively enhance, which you should be doing anyway!
Support for CSS Animation
Browser |
Prefixed support |
Unprefixed support |
Internet Explorer |
N/A |
10+ |
Firefox |
5+ (-moz- ) |
16+ |
Chrome |
4+ (-webkit- ) |
Not supported |
Safari |
4+ (-webkit- ) |
Not Supported |
Opera |
12 (-o- prefix), 15+ (-webkit- prefix) |
12.1 only (not supported since switch to WebKit) |
Again, for keyframe animations, include only the
-webkit-
prefix alongside your unprefixed code.
Evidently, browser support for CSS-based animation is in excellent
shape, but support is slightly more fragmented when it comes to the
nuances of timing functions. The following table explains in more
detail.
Support for CSS Timing Functions
Browser |
Basic support |
cubic-bezier() outside of 0-1 range |
steps() |
Internet Explorer |
10+ |
10+ |
10+ |
Firefox |
4+ |
4+ |
4+ |
Chrome |
4+ |
16+ |
8+ |
Safari |
3.1+ |
6+ |
5.1+ |
Opera |
10.5+ |
12.1+ |
12.1+ |
Again, although certain browsers have taken a little longer to
support the full range of timing-function capabilities, you can see that
support is now universal across current browser versions.
Summary
So, what have we learned about CSS timing functions? Time to recap.
- They define where an animation accelerates and decelerates.
- There is a great deal more to them than just the predefined keywords.
- You can create bounce effects with
cubic-bezier()
values outside of the 0–1 range.
- You can break an animation into any number of steps, rather than tweened motion.
- Browser support is in fantastic shape and ever improving.
Finally, although these techniques are now supported across the
board, this wouldn’t be an article about CSS3 techniques if I didn’t
mention progressive enhancement. Always work from the bottom up; that is
to say, ensure that your work is acceptable and accessible on devices
and browsers that can’t deal with this functionality before enhancing
for browsers that can cope with them.
Other than that, go wild! Happy curving and stepping!
Other Resources