Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 8.2Intermediate ~21 min · shot 2 of 6

The Web Animations API

In one line

element.animate(keyframes, options) starts a CSS-quality animation from JavaScript and hands you an Animation object to steer it. It uses the same engine as CSS animations, with a few different defaults and some silent failures worth knowing.

0/6 · 0%

Think of it like this

A wind-up toy car runs when you let go, and that is all you can do. A remote-controlled car has the same motor, but you hold the controller: stop, reverse, go again. CSS animations are the wind-up car. element.animate() is the same car with a controller.

Words you'll meet

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

Web Animations API (WAAPI)
The browser's JavaScript interface to its animation engine: element.animate(), the Animation object and document.getAnimations().
keyframe
A set of property values at one point (offset) of the animation. The browser fills in the frames between keyframes.
offset
Where a keyframe sits, from 0 (start) to 1 (end). Omit it and the keyframes are spread evenly.
effect
The part of an Animation that holds the keyframes and the timing: animation.effect.
implicit keyframe
A start or end value you did not write: the browser takes it from the element's current computed style.
document.timeline
The clock every animation on the page runs against. currentTime is milliseconds since the page started.

Step by step

01Run the three ways to write the same slide

"Three ways to call animate()" slides three bars. Pause and step: all three are Animation tracks, 800 ms long, cost mark green, and the fourth track is the CSS twin, CSSAnimation · slide. The console prints each one's timing read back from its effect.

02Read the two easing columns

The array form says effect easing ease-in-out because the options object set it. The others say linear. The CSS twin says effect easing linear but keyframe easing ease: CSS puts its default ease on each keyframe. Click each track and compare the graphs.

03Make it fail on purpose

"When animate() goes wrong" runs eight calls. Six throw a TypeError with Chromium's exact words. Two do not throw: the translate(foo) call keeps one keyframe, and the background-color call keeps none. Silence is the dangerous kind.

04Try one keyframe

Change the first example's b call to { transform: "translateX(160px)" }: the bar still slides, from where it is. Add offset: 0 and it starts at 160 px and slides back.

Try it yourself

  1. 1

    Give the object form an easing

    In "Three ways to call animate()", change the number 800 on the b call into { duration: 800, easing: "ease-in-out" }. Its track now says ease-in-out, and the console line says effect easing ease-in-out. Click the track: the graph bends into an S, like the array form's.

  2. 2

    Make the bad value visible

    In "When animate() goes wrong", change translate(foo) to translateX(50px). The line becomes keyframes: translateX(0px), translateX(50px): the second keyframe now survives. Change it back and the missing second value is the only trace of the mistake.

Code & diagrams

Three ways to call animate()

The same slide as an array of keyframes, an object of arrays with a number for the duration, and three keyframes with an offset. The fourth bar is the CSS twin, read back through getAnimations().

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

array form: 800 ms, effect easing ease-in-out, keyframe easing linear, fill none
object form: 800 ms, effect easing linear, keyframe easing linear, fill none
three keyframes: 800 ms, effect easing linear, keyframe easing linear, fill none
CSS twin: 800 ms, effect easing linear, keyframe easing ease, fill none
animations on the page: 4
When animate() goes wrong

Chromium 153's error texts. Other browsers word them differently. The first line: no options means duration 0, already finished.

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

no options -> no error, 0 ms, finished, keyframes: 0, 1
duration as a string -> TypeError: Failed to execute 'animate' on 'Element': duration must be non-negative or auto
negative duration -> TypeError: Failed to execute 'animate' on 'Element': duration must be non-negative or auto
iterations -1 -> TypeError: Failed to execute 'animate' on 'Element': iterationCount must be non-negative
unknown easing -> TypeError: Failed to execute 'animate' on 'Element': 'ease-fast' is not a valid value for easing
offsets out of order -> TypeError: Failed to execute 'animate' on 'Element': Offsets must be monotonically non-decreasing.
bad value -> no error, 100 ms, running, keyframes: translateX(0px), (nothing)
kebab-case name -> no error, 100 ms, running, keyframes: none

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

Forget the duration

In "Three ways to call animate()", change the b call's 800 to {}.

terminal
$ Run it and look at the timeline
── what you'll see ──
The blue bar never moves. Its track is missing from the timeline (a 0 ms animation is finished and gone within one frame) while the other three tracks run.
From a real run.

Myth vs fact

Myth

animate() is slower than CSS because it is JavaScript.

Fact

Only the start is JavaScript. The animation then runs in the same engine as a CSS animation, and a transform or opacity one can still run on the compositor. A hand-written loop is the slow one.

Pro corner

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

  • ▸

    getTiming() returns what you passed, getComputedTiming() the resolved numbers (endTime, progress, currentIteration): use it when a value might be "auto".

  • ▸

    A CSS animation's object is a CSSAnimation, a transition's a CSSTransition: both are Animations, so el.getAnimations()[0].pause() works.

Remember this

  1. 1

    The problem this solves: a CSS animation is declared in a stylesheet, before the page runs. The fly-to-cart dot needs numbers that only exist at click time (where this card is, where the cart is), and the bump must restart on every click. Writing style.transform every frame (Topic 8.1) works but gives up the browser's engine. animate() takes the numbers from JavaScript and keeps the engine.

  2. 2

    **What animate() does.** element.animate(keyframes, options) creates an Animation object, puts it on the page's document.timeline and starts it. It is the same machinery as CSS animations and transitions, so transform and opacity can still run off the main thread (Phase 6). It is supported in every current browser (MDN: widely available since March 2020).

  3. 3

    Keyframes, two shapes. An array has one object per keyframe: [{ transform: "none" }, { transform: "translateX(100px)", offset: 0.25 }, …]. An object of arrays gives each property its list of values, spread evenly: { transform: ["none", "translateX(100px)"] }. A keyframe may carry offset (0 to 1, where it sits), easing (for the segment after it) and composite (Topic 8.5). Property names are camelCase (backgroundColor).

  4. 4

    One keyframe is allowed. el.animate({ transform: "translateX(300px)" }, 500) has no start, so the browser reads the element's current computed style and starts there. This is how a flight can start from wherever the element is now. Add offset: 0 and the single keyframe becomes the start instead.

  5. 5

    Options, in milliseconds. duration (a number, default 0), delay, endDelay, iterations (a number or Infinity, where CSS says infinite), iterationStart, direction, easing, fill, composite, id and pseudoElement. A bare number as the second argument means duration. The default easing is linear.

  6. 6

    Where it differs from CSS. CSS animations default to animation-timing-function: ease, and the engine applies it to *each keyframe segment*, not to the whole animation. Read back, a CSS twin shows effect easing linear and keyframe easing ease; an animate() call shows linear for both unless you set one. So the same 800 ms looks stiffer from JavaScript until you choose an easing (Phase 5).

  7. 7

    Two kinds of mistake behave differently. A bad *timing* value (a string duration, a negative number, an unknown easing, offsets out of order) throws a TypeError at once. A bad *property* value (translate(foo)) or a wrong property *name* (background-color) throws nothing and is silently dropped, and the animation runs with fewer keyframes, or none. On the timeline the track label lacks the property you meant, or there is no track at all.

  8. 8

    **Two things animate() does not do for you.** It ignores prefers-reduced-motion: that media query switches CSS only, so check matchMedia before you call it (Topic 7.3). And on a display: none element it runs (playState is running, getAnimations() lists it) but draws nothing.

  9. 9

    **The Animation object.** It has playState (idle, running, paused, finished), pending (true until the browser has started it, one frame later), effect (the keyframes and timing, with getKeyframes(), getTiming() and getComputedTiming()), currentTime, id, and the promises and methods of the next topics. An animation on an element that is not attached to the page exists but is not listed by document.getAnimations() until you attach it.

Explain it without notes

01

What is the difference between a CSS animation and element.animate() when you do not set an easing?

02

Which mistakes throw an error and which fail silently?

03

What does a single keyframe mean?

Practice

01

Write pulse(el) that scales an element to 1.2 and back over 300 ms with ease-out, using one object-form call.

02

Write a function slideFromHere(el, x) that slides an element to x pixels from wherever it is now, using a single keyframe.

Done when you can

  • You can call animate() with an array, an object of arrays and a number

  • You can name the defaults: duration 0, easing linear, fill none

  • You can say which mistakes throw and which are silent

  • You can read the timing back with getComputedTiming()