Keyframe Animations
A keyframe animation describes motion as a few key moments in a @keyframes rule and plays it with the animation property, so a spinner turns, a title slides in and a Start button pulses with no reader action needed.
Journeyman · CSS · Updated
After this lesson you can
- Write a @keyframes rule with from, to or percentages, and play it with the animation shorthand
- Set an animation's iteration count, direction and fill mode so it loops, goes back and forth or keeps its last frame
- Choose a transition or a keyframe animation for a job, and animate transform and opacity so the motion runs smoothly
Key takeaways
- A @keyframes rule (read "at keyframes") names an animation and lists its key moments, from and to or percentages. The browser draws every frame in between.
- The animation property plays it: animation: pulse 1.6s ease-in-out infinite gives the name, the duration, the timing function and how many times.
- The iteration count says how many times it plays, the direction can make it go back and forth, and the fill mode keeps the first or last frame before or after it plays.
- A transition needs a change, such as hover, and goes between two looks. An animation plays by itself, can have many steps and can loop.
- Animate transform and opacity. The browser can change them without redoing the layout, so they stay smooth where width, top or margin can stutter.
A transition waits for something to change, like the mouse arriving on a button. But a lot of motion on a game screen happens on its own: the loading spinner going around while the level downloads, the title gliding in when the screen opens, the Start button gently pulsing to say "press me". That's a keyframe animation. You'll reach for one whenever something should move without the reader doing anything: a loading indicator, an attention-getting badge, a "new message" bounce or a gentle welcome.
A flipbook: key moments and the frames in between
Draw a stick figure on the corner of every page of a notebook, each one a little different, then flip the pages fast: the figure moves. Animators work the same way. They draw the important poses, the keyframes, and the drawings in between get filled in.
In CSS, you write the keyframes and the browser draws every frame in between. A @keyframes rule (read "at keyframes") gives the animation a name and lists its key moments:
@keyframes twinkle {
from {
opacity: 0.3;
}
to {
opacity: 1;
}
}
That reads "at keyframes twinkle: from opacity 0.3, to opacity 1". It describes a star going from dim to bright. On its own it does nothing yet: it's a flipbook nobody has flipped. from is the start and to is the end; you can also write them as 0% and 100%.
Play it with the animation property
The animation property plays a @keyframes rule on an element. At the least, it needs the name and how long one play takes:
.star {
animation: twinkle 1.5s;
}
The name in animation must match the @keyframes name exactly, capital letters included, and this name is called the animation name. Leave out the duration and the animation lasts 0 seconds, so nothing seems to happen: the duration and the name are the two parts to check first when an animation doesn't play.
The animation property is a shorthand, like transition. Its parts can go in any order, but this order reads well: name, duration, timing function, delay, how many times, direction and fill mode.
.title {
animation: slide-in 600ms ease-out 300ms 1 normal both;
}
As with transitions, the first time is the duration and the second is the delay. Each part has its own property too: animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count, animation-direction and animation-fill-mode.
More key moments: percentages
from and to give two moments. Percentages give as many as you need, each a point in the animation's time. Here's a Start button that swells and settles, again and again:
.start-button {
animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse {
50% {
scale: 1.08;
opacity: 0.85;
}
}
Halfway through each 1.6 seconds, the button is 8% bigger and a little see-through. There's no 0% or 100% here: when a keyframe is missing, the browser uses the element's own style, so the pulse starts and ends at the button's normal size.
A loading spinner is a circle with one colored edge, turning around and around:
.spinner {
width: 3rem;
height: 3rem;
border: 0.4rem solid #2b2d5c;
border-top-color: #ffd23f;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { rotate: 0deg; }
to { rotate: 360deg; }
}
linear keeps the speed the same all the way round, so the spinner never seems to hesitate at the top.
How many times, which way, and what stays
Three parts of the animation decide how it plays:
- The iteration count is how many times it plays:
1(the default),3, orinfinitefor forever. The spinner and the pulse useinfinite. - The direction is which way each play goes:
normal(from start to end),reverse(end to start),alternate(forward, then back, then forward again) andalternate-reverse.twinklewithalternatebrightens, then dims, so the star never jumps from bright back to dim. - The fill mode is what the element looks like when the animation isn't playing. With
none(the default), it goes back to its own style when the animation ends.forwardskeeps the last frame;backwardsshows the first frame during the delay, before it starts; andbothdoes both.
The fill mode matters for the title. It starts off the screen and slides in after a 300ms delay. Without backwards (or both), the title would sit in its normal place for those 300ms, then jump off the screen and slide in again:
.title {
animation: slide-in 600ms ease-out 300ms both;
}
@keyframes slide-in {
from {
translate: 0 -3rem;
opacity: 0;
}
}
Try it. Make a page with a <div class="star"> that has a size and a background color, and give it animation: twinkle 1.5s ease-in-out infinite alternate; with the twinkle keyframes above. Then change infinite to 3, and alternate to normal. Watch the difference: with normal, the star jumps back to dim at the start of each play.
Transition or animation?
Both make things move, so how do you choose?
- A transition goes between two looks when something changes: the mouse arrives, the button gets focus, a class is added. It needs that change to start.
- An animation plays by itself, as soon as the element has it. It can have many key moments, loop forever and run backward.
A button that glows on hover is a transition. A spinner that turns while the game loads is an animation.
Why transform and opacity are smooth
To show a page, the browser does three jobs. Layout works out where every box goes and how big it is. Paint colors in the pixels of each box. Compositing stacks the painted layers into the final picture, like layers in a photo app.
A smooth animation needs a new frame about 60 times a second, so the browser has about 16 milliseconds per frame. Animate width, top or margin, and every frame changes the layout, for this element and often for everything around it: the browser redoes all three jobs every frame, and on a slower phone it can't keep up, so the motion stutters. Animate transform (translate, rotate, scale) or opacity, and the layout doesn't change: the browser keeps the painted layer and moves it or fades it while compositing, often on the graphics chip.
So a loading bar that fills with scale instead of width, and a title that slides with translate instead of top, look the same and run far smoother.
A still picture can't show motion, so the examples here describe it. Paste them into a page and watch them play.
Check yourself
2 questions to try on the lesson page.
So what
With a few key moments and one animation line, you can make a screen feel alive: a spinner that says "wait, it's working", a title that welcomes you in, a button that invites a click. Keep the motion on transform and opacity, and it stays smooth on any device.