Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 4.5Beginner ~22 min · shot 5 of 6

Composition, Play State and Events

In one line

animation-composition decides how an animation combines with the element's own value and with other animations. animation-play-state pauses it. Four events report its life, including animationcancel when it is cut short.

0/6 · 0%

Think of it like this

Two people steering one shopping trolley. If both steer, the trolley follows the one who pushes last (replace), or the pushes can be added together (add). Composition is that rule. A pause button is play state. The events are the trolley's logbook: started, lap done, finished, or taken away halfway.

Words you'll meet

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

composition
How an animation's values combine with the element's own value and with other animations: replace, add or accumulate.
underlying value
The value the element would have with no animation, or with the lower-priority animations applied.
play state
running or paused.
animationcancel
The event that fires when an animation is removed before it finishes.
reflow (forced)
Making the browser work out styles and layout now, for example by reading offsetWidth.

Step by step

01Three rules, three ends

Replay the first example, pause and step to the end: A (replace) is at 40 px, B (add) at 90 px, C (accumulate) at 70 px. Same keyframes, same starting transform.

02Read the events

The second example runs five cases and logs every event. A finished animation gives animationstart, animationend. Removing the class mid-way gives animationstart, animationcancel and no end.

Remove-then-add with no forced update gives one start and one end. With offsetWidth in between, it gives start, cancel, start, end.

03Pause and resume

In the third example the offers scroll. Hover or focus the text, or press Pause: the animation's playState becomes paused, and the console says so. Resume continues from where it stopped.

04Restart it

Read offsetWidth between removing and adding the class.

Try it yourself

  1. 1

    Play the four cases yourself

    In the events example press Remove the class. The log ends with animationcancel, not animationend. Press Let it finish: it ends with animationend. Press Remove, then add and nothing restarts. Press Restart it and you get a second start.

  2. 2

    Make add and accumulate disagree

    In the composition example change the element's own transform to translateX(30px) only and the keyframes to to { transform: translateX(40px); }. Add and accumulate now agree (70 px), because with one function the two rules give the same sum. They only differ when the functions have scale or rotation.

Code & diagrams

replace, add, accumulate

Every box has its own transform. Step to the end and read the final positions: 40, 90 and 70 px.

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.
Events in order, and what cancels

The cases run one after another at load (about six seconds). The buttons run one case each, and the page logs the last five events.

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

finish
animationstart
animationend
remove the class
animationstart
animationcancel
remove and add
animationstart
animationend
remove, force style, add
animationstart
animationcancel
animationstart
animationend
display none
animationstart
animationcancel
animationstart
animationend
Pausing a marquee

Press Pause and the console adds `after the button: paused`. Switch on Reduced motion and the text stops moving and wraps instead.

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

at load: running

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

Clean up on animationend only

In a page that removes an element on animationend, remove the class instead (as the "Remove the class" button does).

terminal
$ Console
── what you'll see ──
animationstart
animationcancel
From the real run above: after the class is removed there is no `animationend`, so the cleanup code never runs.

Break #2

Two animations on transform

On any box add animation: spin 1s linear forwards, drift 1s linear forwards; with @keyframes spin { to { transform: rotate(90deg); } } and @keyframes drift { to { transform: translateX(100px); } }.

terminal
$ Step to the end and read the transform
── what you'll see ──
matrix(1, 0, 0, 1, 100, 0): the box moved 100 px and is not turned. With `animation-composition: replace, add` added: matrix(0, 1, -1, 0, 0, 100).
Both results measured. The turn is not lost with `add`; the move then runs along the turned axis.

Myth vs fact

Myth

animationend always fires.

Fact

Not when the animation is cancelled: removing the class, changing animation-name or hiding the element gives animationcancel instead.

Myth

Removing and re-adding a class restarts the animation.

Fact

Only if the browser sees the removal first, for example after reading offsetWidth. Otherwise nothing changes.

Pro corner

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

  • ▸

    animationend and animationcancel bubble, so one listener on a parent handles children's animations. Check event.target when you only want the parent's.

  • ▸

    display: none cancels, and re-showing restarts an animation whose class is still on. That can replay an entrance every time a tab panel is shown. Use animation-fill-mode and a class set once, or an IntersectionObserver, when you do not want it.

Remember this

  1. 1

    The problem this solves: two animations that both move transform normally overwrite each other, and an animation's end value ignores what the element already has. You need to combine them on purpose, and you need to know when an animation is over (to clean up) or was cut off.

  2. 2

    Composition. animation-composition: replace (default): the animation's value replaces the element's. add: it is combined with the underlying value as a further step. accumulate: the values are summed, function by function. The first example gives each box transform: translateX(30px) scale(1.5) and animates to translateX(40px) scale(2). The end values: replace is at 40 px, scale 2. Add is translateX(30px) scale(1.5) translateX(40px) scale(2): 90 px, scale 3. Accumulate is 70 px, scale 2.5.

  3. 3

    Fighting animations. Two animations on the same property overwrite each other: the later one wins. spin (rotate 90°) and drift (translateX(100px)) on transform end as translateX(100px) only: the rotation is gone. Set animation-composition: replace, add and they end together (a 90° turn and then 100 px along the turned axis, matrix(0, 1, -1, 0, 0, 100)). Or give each its own property: rotate and translate.

  4. 4

    Play state. animation-play-state: paused freezes the animation where it is, and running continues from there, with no restart. It is how you make text that scrolls stop on hover or focus, or let a user pause. Anything that moves on its own for more than 5 seconds needs a way to pause (WCAG 2.2.2), and the example has one.

  5. 5

    Events. animationstart fires when it begins (after any delay). animationiteration fires at the end of each repeat except the last. animationend fires when it finishes. animationcancel fires when it is removed before finishing: the class is removed, animation-name changes, the element gets display: none. **A cancelled animation fires no animationend.** Each event has animationName and elapsedTime, and they bubble from children.

  6. 6

    Restarting. Removing and adding the class in one go does nothing: the browser never saw it removed, so the animation just continues (one animationstart, one animationend). Reading element.offsetWidth between them forces the style update, so the first animation is cancelled and a new one starts. Hiding the element (display: none) cancels the animation, and showing it again starts it afresh if the class is still there.

Explain it without notes

01

Name the three values of animation-composition and say what each does.

02

Which event do you get when an animation's class is removed halfway, and why does that matter for cleanup?

03

How do you pause an animation without restarting it afterwards?

Practice

01

A card has a hover scale(1.05) animation and an entrance slide. Both use transform. Make them work together without conflicts.

02

Write a listener that removes an element after its exit animation, even if the animation is cancelled.

Done when you can

  • You can say how replace, add and accumulate combine values

  • You know which events each way of stopping an animation fires

  • You can restart an animation and know why offsetWidth is needed

  • You can pause and resume without a restart, and give users a pause control