✔︎ Last updated on September 29th, 2022
If you are looking for a complete beginner’s tutorial on CSS animation, this post is for you.
In this article, I cover all the basics of how you can animate HTML elements using CSS.
Along the way, you will create the following bouncing ball animation👇.
I strongly suggest you follow along with this guide in Codepen here.
- The need for Animation
- Setting up the stage
- Define Keyframes
- Targeting elements using animation-name property
- Set a duration using the Animation-duration property:
- animation-iteration-count
- animation-direction
- animation-timing-function
- animation-delay
- animation-fill-mode
- animation-play-state
- keyframes revisited
- animation shorthand property
- 1 Secret Tip to Get Better at CSS Animations
- Conclusion
The need for Animation
Every animal in the animal kingdom (humans included) is equipped to perceive motion subconsciously. In the wild, you can live for another day if you quickly distinguish the moving thing as either predator or prey.
This evolutionary trait is still present in humans even after we have left behind our hunter-gatherer lifestyle.
We can use this trait to make our web pages more intuitive and friendly to users.
For example, in the image below, the text box vibrates and jiggles sharply whenever a user fills in a wrong number.
This snappy, intuitive animation shows the user that the entered number doesn’t belong to this network operator.
I hope you are sold on the importance of animation.
Why CSS Animation?
Simply because – performance and ease of use.
CSS animations are performant in comparison to animations done by JavaScript. Secondly, they are natively supported by all browsers.
And third, they are deceptively easy to implement. You can write code for a CSS animation in less than a minute without knowing anything single line of JavaScript.
Let’s see how.
Setting up the stage
In this bouncing ball animation, we have two animated elements –
- The bouncing ball, and
- the shadow that grows and shrinks in rhythm with the ball.
Therefore, in the code below, I have created a ball and a shadow. I have also centered them on the page using some basic CSS.
This will be my HTML code –
<div class="box">
<div class="ball"></div>
<div class="shadow"></div>
</div>
And this will be my CSS –
body {
height: 400px;
/* Centers the ball and the shadow on the page. */
display: flex;
justify-content: center;
align-items: end;
}
.ball {
/* Creates a ball of blue color */
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #0052ff;
}
.shadow {
/* creates a shadow */
width: 100px;
margin: auto;
box-shadow: 0px 0px 15px 2px #111;
}
Here’s the output –
<div class="box"> <div class="ball"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; } .shadow { /* creates a shadow */ width: 100px; box-shadow: 0px 0px 15px 2px #111; }
Now let’s come to the basics of CSS animation.
Define Keyframes
The word keyframe is made up of two words – Key + Frame.
This term became popular in the animation industry in the early 20th century. In traditional animation, before the invention of computers, every frame of a sequence was drawn by hand.
It means that for a smooth-looking 24fps video, the artist had to draw (by hand!) 24 frames for every second of the video. Needless to say, it was a tedious and cumbersome task for the animators.
It is therefore that the animation studios like Disney would hire background artists who could quickly create the intermediate frames.
The lead animator would create two frames, one for the start and one for the end. The background artists would then create in-between frames.
For example, suppose a character is to be moved from Point A to Point B. The lead animator would draw these two frames (called keyframes).
Then, the background artists would draw these in-between frames.
The number of these in-between frames would be determined by the duration and framerate of the sequence.
The same concept is used in CSS animation except that here,
We will define two (or more) keyframes and the browser will draw the intermediate frames by itself using some mathematical calculations.
Now the question arises, how do we tell the browser what our keyframes are? CSS has a remarkably simple notation —
@keyframes animation-name {
from {
/* current values */
}
to {
/* new values */
}
}
Simply, we tell the browser to change the values of the element’s current properties to new values in a given duration. Note that from
and to
are CSS keywords. You can actually use them in your keyframe definitions.
We can also define keyframes in percentages, like this —
@keyframes animation-name {
0% {
/* example */
transform: translateY(-400px);
}
80% {
transform: translateY(-200px);
}
100% {
transform: translateY(0);
}
}
In the above code, we are telling the browser that when the animation starts (0%), move the element up 400px in -y
direction. Then, in the first 80%
of the duration, move it down by 200px
. And then, drop it to 0px
in the remaining 20%
duration.
The order of writing keyframes doesn’t matter. So this should also work –
@keyframes animation-name {
100% {
width: 200px;
}
80% {
width: 500px;
}
0% {
width: 200px;
}
}
Also, you can omit writing both frames (0%
and 100%
). The browser will automatically set properties’ values to their defaults (or to the values specified by you in your CSS stylesheet).
So, this code –
@keyframes animation-name {
50% {
transform: translateY(-400px);
}
}
is equivalent to this code –
@keyframes animation-name {
0% {
/* default value */
transform: translateY(0px);
}
50% {
transform: translateY(-400px);
}
100% {
/* default value */
transform: translateY(0px);
}
}
Also, if two keyframes are the same, you can write them comma-separated. So, the above code is equivalent to —
@keyframes animation-name {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-400px);
}
}
Once we define our keyframes, we need to link them to an element. By linking them, the browser knows which element to apply the keyframes to.
We can do that by using the animation-name
property.
Targeting elements using animation-name
property
Once you have defined your keyframes, it is time to tie them up with a target element. This way, the browser knows which element to animate using those keyframes.
We use it like this –
Now that we have learned how to define keyframes and link them to a target element, let us proceed to write some actual code for our bouncing ball animation.
In the first frame, I will raise the ball to a height of 400px
, and in the last frame, I will let it fall by 400px
.
This will be our keyframe definition —
@keyframes bounce {
0% {
transform: translateY(-400px)
}
/* The following keyframe is written just for the sake of completeness. The animation works even if we omit it. */
100% {
transform: translateY(0px)
}
}
<div class="box"> <div class="ball"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); } } .shadow { /* creates a shadow */ width: 100px; box-shadow: 0px 0px 15px 2px #111; }
Oops! the animation doesn’t run! Why?
Because we haven’t specified a duration yet.
Set a duration using the Animation-duration
property:
Let’s set the animation-duration
to 1 second.
We can do that like this –
.ball {
/* write code so far here */
animation-duration: 1s;
}
<div class="box"> <div class="ball"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; animation-duration: 1s; } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); } } .shadow { /* creates a shadow */ width: 100px; box-shadow: 0px 0px 15px 2px #111; }
Now it works. Perfect!
Also note that we can also set duration in decimals, like 1.3 seconds (1.3s
) or in milliseconds (like 1300ms
). Go ahead, try these in the Codepen.
Also, the animation-duration
property doesn’t accept negative values (because negative duration doesn’t make sense).
This animation is nowhere close to our desired bouncing ball. It has several issues. Let’s fix them one by one.
First, it feels a little slow. The ball is taking too much time to hit the floor.
In my testing and tweaking, I have found that the duration of 0.65
seconds feels a bit more natural. So, let’s set the animation-duration
to 0.65
.
The second issue with this animation is that the animation stops after running only once. I want it to run forever. What to do?
animation-iteration-count
Well, we are in luck! We can use the animation-iteration-count
property to define the number of times we want this animation to work.
We can set animation-iteration-count
to any positive integer. We can even set it to infinite
to run the animation indefinitely.
.ball {
/* write code so far here */
animation-iteration-count: infinite;
}
<div class="box"> <div class="ball"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; animation-duration: 1s; animation-iteration-count: infinite; } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); } } .shadow { /* creates a shadow */ width: 100px; box-shadow: 0px 0px 15px 2px #111; }
animation-direction
There’s another problem. This ball is only falling, it’s not bouncing back. Why?
Because in every iteration, the animation is only going forward (from 0% to 100%).
What we want is that it should first go in the normal
direction i.e. from 0% to 100% and then it should go in reverse
i.e. from 100% to 0%. In other words, we want it to alternate
between the normal
and reverse
direction after every iteration.
We can do that by animation-direction
property.
.ball {
/* write code so far here */
animation-direction: alternate;
}
<div class="box"> <div class="ball"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; animation-duration: 1s; animation-iteration-count: infinite; animation-direction: alternate; } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); } } .shadow { /* creates a shadow */ width: 100px; box-shadow: 0px 0px 15px 2px #111; }
To summarize, the property animation-direction
can take four values —
Go ahead and play with these values in the Codepen.
We are much close to our desired animation but there’s still a problem.
The bounce of the ball doesn’t feel natural.
That’s not how objects in the real-world fall. We need a way to incorporate the effects of gravity.
Enter animation-timing-function
.
animation-timing-function
Using this property, we can define how the animation progresses. We can adjust the speed curve of animation throughout the duration.
In the physical world, when a ball falls, it starts slow. Its speed gradually increases because of gravity until it hits the ground. As the ball progresses downward, it increasingly covers more distance per unit of time.
We can tell the browser all this using the property animation-timing-function
.
We set it like this –
.ball {
/* write code so far here */
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.22);
}
Cubic-bezier
You might be wondering what this cubic-bezier
is.
Cubic-bezier curve is a mathematical curve that has 4 points. It looks like this –
P0 is the first point which is set at (0,0) and P3 is the last point which is set at (1,1).
P1 and P2 are control points of the format (x,y)
. Here, x
denotes the time and y denotes the progress of the animation.
Using the cubic-bezier
function, we define our control points P1 and P2, in the format – (x1, y1, x2, y2)
.
The curve with values cubic-bezier(0.33, 0, 0.66, 0.22)
looks like this –
As you can see in the graph above, the animation starts slow, and gradually, it picks up speed as it nears the end (similar to the speed of the falling ball).
Now that we have covered the theory, let’s plug in the values and see if it works.
<div class="box"> <div class="ball"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; animation-duration: 0.65s; animation-iteration-count: infinite; animation-direction: alternate; animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.22); } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); } } .shadow { /* creates a shadow */ width: 100px; box-shadow: 0px 0px 15px 2px #111; }
It does.
I want to highlight here that the cubic-bezier
function is not intuitive so you will have to play around with the values to get the desired effect. I myself got these values only after tinkering with them for about 5 minutes.
You can use tools like www.cubic-bezier.com to get an idea.
Also, CSS ships with several commonly used values of the cubic-bezier function predefined in CSS.
The same animation with other easings looks like this (👇) —
<div class="box"> <!-- <h3>Ease</h3> --> <div class="ball ease"></div> <div class="shadow"></div> <h2>ease</h2> </div> <div class="box"> <div class="ball ease-in"></div> <div class="shadow"></div> <h2>ease-in</h2> </div> <div class="box"> <div class="ball ease-out"></div> <div class="shadow"></div> <h2>ease-out</h2> </div> <div class="box"> <div class="ball ease-in-out"></div> <div class="shadow"></div> <h2>ease-in-out</h2> </div> <div class="box"> <div class="ball linear"></div> <div class="shadow"></div> <h2 style="margin-left: 30px">linear</h2> </div> <div class="box"> <div class="ball ball-5"></div> <div class="shadow"></div> <h2 class="cubic-bezier">Cubic-bezier(0.33, 0, 0.66, 0)</h2> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; animation-duration: 0.65s; animation-iteration-count: infinite; animation-direction: alternate; animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.22); } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); } } .shadow { /* creates a shadow */ width: 100px; box-shadow: 0px 0px 15px 2px #111; } h2 { position: absolute; } /* Easings */ .ease { animation-timing-function: ease; } .ease-in { animation-timing-function: ease-in; } .ease-out { animation-timing-function: ease-out; } .ease-in-out { animation-timing-function: ease-in-out; } .linear { animation-timing-function: linear; }
Now it’s time to animate the shadow.
Notice that the shadow grows and shrinks in tune with the fall and bounce of the ball.
Therefore, the value of properties like animation-duration
, animation-timing-function
, animation-iteration-count
, and animation-direction
should be the same as that of the ball so that these two are always in sync with each other (👇).
<div class="box"> <div class="ball ease"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; } .shadow { /* creates a shadow */ width: 100px; margin: auto; box-shadow: 0px 0px 15px 2px #111; animation-name: shrink-and-grow; } .ball, .shadow { animation-duration: 0.65s; animation-iteration-count: infinite; animation-direction: alternate; animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.22); } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); } } @keyframes shrink-and-grow { 0% { width: 20px; } }
Our animation is almost complete. But we have still not discussed all the animation properties CSS has to offer.
There are three remaining properties – animation-delay
and animation-fill-mode
and animation-play-state
.
animation-delay
As you might have guessed, we can use this property to delay an animation.
Similar to animation-duration
, it accepts time values either in seconds (1s
) or in milliseconds (1000ms
).
It is particularly useful when we have applied two or more animations used on a page and we want one to play after the other.
<div class="box"> <div class="ball ease"></div> <div class="shadow"></div> </div> <div class="box"> <div class="ball delayed"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; } .shadow { /* creates a shadow */ width: 100px; margin: auto; box-shadow: 0px 0px 15px 2px #111; animation-name: shrink-and-grow; } .ball, .shadow { animation-duration: 0.65s; animation-iteration-count: infinite; animation-direction: alternate; animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.22); } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); } } @keyframes shrink-and-grow { 0% { width: 20px; } } /* second ball wil be delayed */ .delayed { animation-delay: 0.35s; }
Animation duration also accepts negative values. The negative values make the animation start earlier than usual.
animation-fill-mode
By default, the styles defined in keyframe definitions don’t apply to an element when the animation is not playing. We can use animation-fill-mode
property to override this behavior.
We can define what styles remain applied to the target element when the animation ends. It accepts three values – forwards
, backwards
and both
.
<div class="box"> <div class="ball forwards"></div> <div class="shadow"></div> <h3>Forwards</h3> </div> <div class="box"> <div class="ball backwards"></div> <div class="shadow"></div> <h3>Backwards</h3> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; } .shadow { /* creates a shadow */ width: 100px; margin: auto; box-shadow: 0px 0px 15px 2px #111; animation-name: shrink-and-grow; } .ball, .shadow { animation-duration: 0.65s; animation-iteration-count: 3; animation-direction: alternate; animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.22); } @keyframes bounce { 0% { transform: translateY(-400px); } 100% { transform: translateY(0px); background-color: red; } } @keyframes shrink-and-grow { 0% { width: 20px; } } .forwards { animation-fill-mode: forwards; } .backwards { animation-fill-mode: backwards; }
animation-play-state
We can use animation-play-state
property to pause or play the animation.
This property accepts two values –
animation-play-state
property is particularly useful when we want to play or pause an animation via javascript. For example, we might want to keep an animation paused until the element scrolls into the view or until the user fires an event such as a button click.
We have almost covered all the CSS animation properties but we can improve our bouncing ball animation a little bit more.
keyframes
revisited
In real life, when a ball strikes the ground, it stretches and squashes a little and then regains its shape as it bounces back.
We can implement that by decreasing the height
(squash) and increasing the width
(Stretch) of the ball just as it strikes the ground.
<div class="box"> <div class="ball forwards"></div> <div class="shadow"></div> </div>
body { height: 400px; /* Horizontally centers the ball and the shadow. */ display: flex; justify-content: center; align-items: end; } .ball { /* Creates a ball of blue color */ width: 100px; height: 100px; border-radius: 50%; background-color: #0052ff; /* link the element with keyframes */ animation-name: bounce; } .shadow { /* creates a shadow */ width: 100px; margin: auto; box-shadow: 0px 0px 15px 2px #111; animation-name: shrink-and-grow; } .ball, .shadow { animation-duration: 0.65s; animation-iteration-count: infinite; animation-direction: alternate; animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.22); } @keyframes bounce { 0% { transform: translateY(-400px); } /* dummy frame so that width/height change only when the ball strikes the ground and not before that. */ 90% { width: 100px; height: 100px; } 100% { transform: translateY(0px); /* stretch and squash */ width: 120px; height: 82px; } } @keyframes shrink-and-grow { 0% { width: 20px; } }
This looks good enough.
animation
shorthand property
You might be wondering if it isn’t too much work writing all seven properties with their lengthy names.
You are right. It is.
That’s why CSS provides the animation
shorthand property using which you can set all properties at once.
It is a shorthand for name | duration | easing-function | delay |
iteration-count | direction | fill-mode | play-state
The order of writing these properties doesn’t matter that much except that you should always write the animation-duration
before the animation-delay
value. You can read more about the animation shorthand property on MDN.
1 Secret Tip to Get Better at CSS Animations
There’s a tool called Animate | Keyframes.app. Using this tool, you can easily learn to animate any element in a visual manner.
When you open the app, you are presented with an element and a number of different animatable properties on the right side. On the bottom bar, you have the option to set CSS animation properties like duration, easings, timing-function, iteration-count
and so on.
At the bottom, there is an option to Add Stop
using which you can define keyframes. At every stop, you can set the values of properties and the browser will play the animation when you hit the green play button on the right bottom side.
You can also get its CSS code by clicking on the green Get CSS button on the top bar.
Since everything is laid out visually, I believe it is super useful for beginners who have just started learning CSS animations.
Conclusion
In this post, we learned how to animate an element in CSS.
First, we define keyframes either using the keywords from
and to
or by using percentages. Then we tie them to a target element using animation-name
property so that the browser knows which element to animate. And then we set a duration using animation-duration
.
Remember that the animation won’t run unless we specify a non-zero duration because the default duration in CSS is fixed at 0
.
You can use other CSS animation properties to further fine-tune your animations, –
In this post, we also learned about a visual CSS animation tool called Keyframes.app.
I have more step-by-step tutorials on CSS animations which you might find super helpful. Here are some of them —
I hope you enjoyed this guide. If you liked it, please drop a line below in the comments.
This is a fantastic guide filled with extraneous resources and a great mindset to learning CSS animations!