99

Concurrent visitors over the last 30 minutes. (~)

Peak concurrent visitors -
Yesterday: 0 / Today: 0

The Complete Guide to CSS Animation

The Complete Guide to CSS Animation

CSS Animation is a technique that adds animation effects by controlling an element's size, color, shape, and more with various CSS style properties.
Because you can easily add dynamic effects to elements without using JavaScript, you can implement animations with better performance.

# Transition vs animation

A CSS transition smoothly changes an element from one state to another, and it is simple to use because you only need to think about the start and end states.
An animation, on the other hand, can also handle intermediate states, not just the start and end, so you can add more complex and varied effects.

Transition vs Animation / Hover over the elements.

# @keyframes

@keyframes is a CSS rule that defines 'animation frames'.
You can define the start and end styles of an animation with the from and to keywords, or define each stage of the animation with percentage values from 0% to 100%.

CSS
1
2
3
4
5
6
7
8
9
10
11
12
/* from-to */ @keyframes name { from {} to {} } /* percentage */ @keyframes name { 0% {} 50% {} 100% {} }

# animation

animation is a shorthand property for the individual animation control properties.
The animation name and duration are required, and the other properties are optional.

CSS
1
animation: name duration delay? timing-function? iteration-count? direction? fill-mode? play-state?;
Property Meaning Default
animation-name Name of the @keyframes rule none
animation-duration Duration 0s
animation-delay Delay 0s
animation-timing-function Timing function ease
animation-iteration-count Iteration count 1
animation-direction Iteration direction normal
animation-fill-mode State before and after none
animation-play-state Play and pause running

# animation-name

Specifies the name of the @keyframes rule (animation frames) to use.

Value Meaning Default
none No animation none
@keyframes name Applies the animation of the @keyframes rule with the matching name

In the following example, when you hover over an element, it applies the animation frames named heropy (the element's width changes from 300px to 500px).
For the animation frames to actually run, you also need to specify how many seconds they last.

CSS
1
2
3
4
5
6
7
8
9
10
.item:hover { /* animation: heropy 2s; */ animation-name: heropy; animation-duration: 2s; } @keyframes heropy { 0% { width: 300px; } 100% { width: 500px; } }
Hover over the element.

# animation-duration

Specifies the duration of the animation frames.
The duration is the time it takes for the animation to go from start to finish.

Value Meaning Default
Time Sets the duration 0s
CSS
1
2
3
4
5
6
7
8
9
10
.item:hover { animation-name: heropy; } .item:nth-child(1):hover { animation-duration: 2s; } .item:nth-child(2):hover { animation-duration: 8s; } @keyframes heropy { 0% { width: 300px; } 100% { width: 500px; } }
Hover over the elements.

If you use the animation shorthand property as follows, it overwrites the existing property and drops the animation frame name, so the animation does not run.

CSS
1
2
3
4
5
.item:hover { animation: heropy; } .item:nth-child(1):hover { animation: 2s; } .item:nth-child(2):hover { animation: 8s; }

# animation-delay

Sets the delay, which is how long to wait before the animation starts.
If you use a negative time, the animation starts that much ahead.

Value Meaning Default
Time Sets the delay 0s
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
.item:hover { /* animation: heropy 2s; */ animation-name: heropy; animation-duration: 2s; } .item:nth-child(1):hover { animation-delay: 0s; } .item:nth-child(2):hover { animation-delay: 1s; } .item:nth-child(3):hover { animation-delay: -1s; } @keyframes heropy { 0% { width: 300px; } 100% { width: 500px; } }
Hover over the elements.

# animation-timing-function

Specifies the timing function to use in the animation.

A timing function, also called an easing function, decides how an animation progresses by adjusting its acceleration and deceleration from start to finish.

Value Meaning Default Cubic Bezier value
linear Constant cubic-bezier(0, 0, 1, 1)
ease Fast - slow ease cubic-bezier(0.25, 0.1, 0.25, 1)
ease-in Slow - fast cubic-bezier(0.42, 0, 1, 1)
ease-out Fast - slow cubic-bezier(0, 0, 0.58, 1)
ease-in-out Slow - fast - slow cubic-bezier(.42, 0, .58, 1)
cubic-bezier(n1, n2, n3, n4) Defines your own values (from 0 to 1)
steps(n, j?) Animation divided into n steps
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.item { /* animation: heropy 3s infinite; */ animation-name: heropy; animation-duration: 3s; animation-iteration-count: infinite; } .item:nth-child(1) { animation-timing-function: linear; } .item:nth-child(2) { animation-timing-function: ease; } .item:nth-child(3) { animation-timing-function: ease-in; } .item:nth-child(4) { animation-timing-function: ease-out; } .item:nth-child(5) { animation-timing-function: ease-in-out; } .item:nth-child(6) { animation-timing-function: cubic-bezier(0.83, 0, 0.5, 1.73); } .item:nth-child(7) { animation-timing-function: steps(4); } @keyframes heropy { 0% { width: 300px; } 100% { width: 500px; } }

# cubic-bezier

The CSS cubic-bezier() function controls the timing and speed of an animation by defining a Bezier curve made up of two end points and two control points.

CSS
1
cubic-bezier(n1, n2, n3, n4)

With the 'cubic bezier editor' in Chrome DevTools, you can adjust the value of each point and see the animation's timing function visually.

Select the icon next to the timing function value

Select the arrow buttons at the bottom of the editor

Check the various values

# steps

The CSS steps() function sets how many steps to divide the animation into.

CSS
1
steps(count, jump-timing?)
Value Meaning Default
jump-start or start Skips the start (0%)
jump-end or end Skips the end (100%) jump-end
jump-both Skips both the start and the end
jump-none Skips nothing

The following example uses the steps() function to divide an animation that moves from left to right into 5 steps.
You can see that the start and end points differ depending on each jump timing.

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.item { width: 150px; animation-name: heropy; animation-duration: 3s; animation-iteration-count: infinite; } .item:nth-child(1) { animation-timing-function: steps(5, jump-start); } .item:nth-child(2) { animation-timing-function: steps(5, jump-end); } /* default */ .item:nth-child(3) { animation-timing-function: steps(5, jump-both); } .item:nth-child(4) { animation-timing-function: steps(5, jump-none); } @keyframes heropy { 0% { transform: translateX(0px); } 100% { transform: translateX(500px); } }

# animation-iteration-count

Specifies how many times the animation repeats.

Value Meaning Default
Number Sets the iteration count 1
infinite Repeats forever
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
.item:hover { /* animation: heropy 2s; */ animation-name: heropy; animation-duration: 2s; } .item:nth-child(1):hover { animation-iteration-count: 1; } .item:nth-child(2):hover { animation-iteration-count: 2; } .item:nth-child(3):hover { animation-iteration-count: 3; } @keyframes heropy { 0% { width: 300px; } 100% { width: 500px; } }
Hover over the elements.

# animation-direction

Specifies the direction in which the animation repeats.
For example, in an animation where an element moves from left to right, moving from left to right is the 'forward' motion, and moving from right to left is the 'reverse' motion.
Moving both forward and in reverse is called a 'round trip', and for a round-trip animation, the value of the animation-iteration-count property (iteration count) must be 2 or more.

Value Meaning Default
normal Repeats forward only normal
reverse Repeats in reverse only
alternate Round trip, starting forward
alternate-reverse Round trip, starting in reverse
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.item:hover { /* animation: heropy 2s 2; */ animation-name: heropy; animation-duration: 2s; animation-iteration-count: 2; } .item:nth-child(1):hover { animation-direction: normal; } .item:nth-child(2):hover { animation-direction: reverse; } .item:nth-child(3):hover { animation-direction: alternate; } .item:nth-child(4):hover { animation-direction: alternate-reverse; } @keyframes heropy { 0% { width: 300px; } 100% { width: 500px; } }
Hover over the elements.

# animation-fill-mode

Specifies the state (style) of the element before and after the animation.
It decides which style to keep before and after the animation: the element's own default style or the applied animation style.
If you want only the animation style to be shown, specify the both value, which ignores the default style.

Value Meaning Default
none Default state -> Animation start -> Animation end -> Default state none
forwards Default state -> Animation start -> Animation end
backwards Animation start -> Animation end -> Default state
both Animation start -> Animation end
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.item { /* animation: heropy 2s 1s; */ animation-name: heropy; animation-duration: 2s; animation-delay: 1s; } .item:nth-child(1) { animation-fill-mode: none; } .item:nth-child(2) { animation-fill-mode: forwards; } .item:nth-child(3) { animation-fill-mode: backwards; } .item:nth-child(4) { animation-fill-mode: both; } @keyframes heropy { 0% { transform: translateX(230px) rotate(0deg); } 50% { transform: translateX(230px) rotate(360deg); } 85% { transform: translateX(500px) rotate(360deg); } 100% { transform: translateX(500px) rotate(360deg); } }
Select the start button to run that animation.

# animation-play-state

Specifies whether the animation plays or pauses.

Value Meaning Default
running Plays the animation running
paused Pauses the animation
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.item { /* animation: heropy 2s infinite alternate; */ animation-name: heropy; animation-duration: 2s; animation-iteration-count: infinite; animation-direction: alternate; } .item:nth-child(1) { /* animation-play-state: running; */ } .item:nth-child(2) { animation-play-state: paused; } .item:nth-child(1):hover { animation-play-state: paused; } .item:nth-child(2):hover { animation-play-state: running; } @keyframes heropy { 0% { width: 300px; } 100% { width: 500px; } }
Hover over the elements.

# animation-composition

Specifies how to combine animation styles.

Value Meaning Default
replace Replaces the default style with the animation style replace
add Adds the animation style to the default style
accumulate Mixes the default style and the animation style

In the following example, the element is 100 wide and 50 tall by default, moved 50 to the right, and twice its size.
If the value of the animation-composition property is replace (the default), the default style is replaced by the animation style, so the element is moved 100 to the right and is twice its size.
If the value is add, the animation style is added to the default style, so the element is moved 250 (50+100*2) to the right and is 4 (2+2) times its size.
If the value is accumulate, the default style and the animation style are mixed, so the element is moved 150 (50+100) to the right and is 3 times its size.

Why the element is 3 times (scale(3)) rather than 4 times its size when the value is accumulate: 'The mysterious case of using CSS animation-composition: accumulate on a scale transform'

CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.item { width: 100px; height: 50px; /* animation: heropy 3s both; */ animation-name: heropy; animation-duration: 3s; animation-fill-mode: both; transform: translateX(50px) scale(2); transform-origin: 0 0; } .item:nth-child(1) { animation-composition: replace; } .item:nth-child(2) { animation-composition: add; } .item:nth-child(3) { animation-composition: accumulate; } @keyframes heropy { 0% { transform: translateX(50px) scale(1); } 100% { transform: translateX(100px) scale(2); } }
The element's default style combined with the animation style / Try selecting each button at the top.

The animation-composition property is available in Chrome from version 112.

caniuse

# Animation examples

# Simple spinner

HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style> .hloader { --hloader-color: #e96900; --hloader-size: 80px; --hloader-weight: 8px; --hloader-duration: 1s; width: var(--hloader-size); height: var(--hloader-size); color: var(--hloader-color); border: var(--hloader-weight) solid; border-top-color: transparent; border-radius: 50%; box-sizing: border-box; animation: hloader var(--hloader-duration) infinite linear; } @keyframes hloader { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> <div class="hloader"></div>

# Spinner

HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<style> .hloader { --hloader-color: #e96900; --hloader-size: 100px; --hloader-weight: 4; --hloader-duration: 1.5s; width: var(--hloader-size); height: var(--hloader-size); color: var(--hloader-color); } .hloader svg { animation: hloader-svg var(--hloader-duration) linear infinite; } .hloader circle { animation: hloader-circle var(--hloader-duration) linear infinite; fill: transparent; stroke-width: var(--hloader-weight); stroke-dasharray: 80, 200; stroke-dashoffset: 0px; stroke: currentColor; } @keyframes hloader-svg { 100% { transform: rotate(1turn); } } @keyframes hloader-circle { 0% { stroke-dasharray: 1, 200; stroke-dashoffset: 0px; } 50% { stroke-dasharray: 100, 200; stroke-dashoffset: -15px; } 100% { stroke-dasharray: 100, 200; stroke-dashoffset: -124px; } } </style> <div class="hloader"> <svg viewBox="22.857142857142858 22.857142857142858 45.714285714285715 45.714285714285715"> <circle cx="45.714285714285715" cy="45.714285714285715" r="20" stroke-width="5.714285714285714" stroke-dasharray="125.664" stroke-dashoffset="125.66370614359172px"></circle> </svg> </div>

# Skeleton loader

HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<style> .hskeleton { background-color: #222; border-radius: 4px; position: relative; overflow: hidden; } .hskeleton::after { content: ""; width: 100%; height: 100%; position: absolute; top: 0; left: 0; background-image: linear-gradient(270deg, rgba(255,255,255,0), rgba(255,255,255,.1), rgba(255,255,255,0) ); transform: translateX(-100%); animation: hskeleton-loader 2s infinite; } @keyframes hskeleton-loader { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } </style> <div class="hskeleton"></div>