Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 4.2Beginner ~19 min · shot 2 of 6

@keyframes and the animation Property

In one line

@keyframes lists the states an animation passes through, and the animation property starts it and sets its timing. Missing stops and missing names have exact, predictable results.

0/6 · 0%

Think of it like this

A flip-book. Each page is a still drawing, and flipping shows them one after another. A keyframe is one page you draw yourself, the key ones. The browser draws every page in between. (The CSS course's Topic 10.3 showed the basics. Here: every part, and every gap.)

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

keyframe
One stop in an animation: a point in its time and the values the element has there.
@keyframes
The CSS rule that names an animation and lists its keyframes.
shorthand
A property that sets several others at once, such as animation. It also resets the ones you leave out.
longhand
One of the single properties a shorthand sets, such as animation-delay.
stop
A percentage inside @keyframes, such as 70%.

Step by step

01Two animations at once

Press Ping the headlight. The dot slides 200 px (900 ms) and, starting 300 ms in, pulses its shadow twice (600 ms each). The console says animations on the dot: slide + ping, the order they are listed in.

Slow it to 0.1× and read the tracks: slide starts at 0, ping has delay 300 and ×2.

02Read the shorthand

ping 600ms ease-out 300ms 2 is: name, duration 600 ms, easing, delay 300 ms, iteration count 2. Two time values: the first is always the duration.

03Look at the gaps

The second example logs what the browser built. A misspelt name gives animations created = 0. A single 50% stop gives three keyframes, with none at both ends. A lone to starts from the element's own translateX(20px).

04Order of the shorthand and the longhands

Write .box { animation-fill-mode: forwards; } and then .box { animation: slide 1s; }. The second rule resets the fill mode to none: the box snaps back at the end. Swap the two rules and it holds.

Try it yourself

  1. 1

    Swap the two time values

    In the first example change ping 600ms ease-out 300ms 2 to ping 300ms ease-out 600ms 2. Press Run and the button. The track now says delay 600, duration 300: the first time is the duration, the second the delay.

  2. 2

    Watch the later animation win

    In the second example, step the clock to the end. The "two animations" box stops at 30 px (x2), not 100 px (x1): both animate transform, and the later one in the list wins.

Code & diagrams

Two animations on one dot

Press the button, pause, and read the two tracks. The ping track is amber: box-shadow costs a repaint.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

animations on the dot: slide + ping
What the browser does with gaps

Four edge cases in one page. The first row has no track at all: its animation was never created.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

misspelt name: animations created = 0
only 50%: [[0,"none"],[0.5,"translateX(100px)"],[1,"none"]]
only to: [[0,"translateX(20px)"],[1,"translateX(100px)"]]
two on one property: x1, x2

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Misspell the animation name

In the second example, change .go #mid { animation: half 1s linear; } to animation: halff 1s linear; and press Run.

terminal
$ Console and timeline
── what you'll see ──
No error. The "only a 50% stop" box does not move, and its track is missing. `show("mid")[0]` is undefined, so the script stops with a TypeError.
Matches the first row: a name with no @keyframes creates no animation, and nothing is reported by CSS.

Break #2

A shorthand that wipes the fill mode

In the second example, add .go #nofrom { animation-fill-mode: forwards; } before the .go #nofrom { transform: …; animation: tox 1s linear forwards; } rule, and take forwards out of that rule.

terminal
$ Step to the end
── what you'll see ──
The box ends back at 20 px instead of holding at 100 px.
Measured with the same pattern: after the shorthand, `animation-fill-mode` computes to `none`.

Myth vs fact

Myth

A missing @keyframes gives an error in the console.

Fact

It gives nothing. The animation is simply not created, which makes typos hard to spot.

Myth

Percent stops are milliseconds.

Fact

They are fractions of one iteration. The same keyframes run at any duration.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Each @keyframes name belongs to the stylesheet's scope, and a later rule with the same name replaces the earlier one completely, stops and all. Prefix names (toast-in) to avoid clashes between components.

  • ▸

    Setting animation-name: none and back on the next frame restarts an animation: the first change cancels it (an animationcancel event), the second starts it. void el.offsetWidth between a class removal and addition does the same.

  • ▸

    Keyframes can set animation-timing-function per stop: it applies from that stop to the next, so one animation can ease out of one stop and linear into the next.

Remember this

  1. 1

    The problem this solves: a transition only goes from the old value to the new one, and only when something changes. Some motion has more than two states (a pulse that grows, then fades) or must start by itself, repeat, or run when a class is added. Keyframes give you any number of states.

  2. 2

    Keyframes. @keyframes ping { 0% {…} 70% {…} 100% {…} } names the animation and lists stops. from is 0%, to is 100%, and you may list stops together (70%, 100% { … }). Stops are positions in the animation's time, not in milliseconds: the duration comes from the animation property.

  3. 3

    Starting it. animation: slide 900ms cubic-bezier(0.22, 1, 0.36, 1) forwards is the shorthand. The first time value is the duration, the second is the delay. The longhands are animation-name, -duration, -timing-function, -delay, -iteration-count, -direction, -fill-mode, -play-state and -composition. A comma list runs several animations on one element, as the first example does with slide and ping.

  4. 4

    The shorthand resets everything it leaves out. animation: slide 1s sets fill mode back to none, even if an earlier rule said forwards. Put longhands after the shorthand, not before. This is a very common reason for an animation that "stopped holding its end state".

  5. 5

    Gaps fill in from the element. If a keyframe lacks a property at 0% or 100%, the browser uses the element's own value for it. If an animation lists a property in only some stops, the property is interpolated between the stops that have it. If you name a @keyframes that does not exist, nothing happens and nothing is reported: no animation is created.

  6. 6

    Two animations, one property. The later one in the list wins that property at each moment. Its missing from starts from where the earlier one is, which is why their combination can look odd. Topic 4.5 shows how to combine them on purpose.

Explain it without notes

01

What happens when an animation's keyframes have no from stop?

02

Why can a shorthand animation rule break an earlier animation-fill-mode?

03

What do you get when you name a @keyframes rule that does not exist?

Practice

01

Write keyframes pulse that scale a badge to 1.2 at 50% and back, used as animation: pulse 800ms ease-in-out 3. What stops does the browser create for 100%?

02

A button's pulse works in one file but not in another. List three things to check.

Done when you can

  • You can write @keyframes with from, to and percent stops

  • You can read the shorthand: duration first, delay second

  • You know what the browser does with missing stops and missing names

  • You can run two animations on one element and say which wins