Shots in this scene
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.
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
Give the object form an easing
In "Three ways to call animate()", change the number
800on thebcall into{ duration: 800, easing: "ease-in-out" }. Its track now saysease-in-out, and the console line sayseffect easing ease-in-out. Click the track: the graph bends into an S, like the array form's. - 2
Make the bad value visible
In "When animate() goes wrong", change
translate(foo)totranslateX(50px). The line becomeskeyframes: 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
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().
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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: 4Chromium 153's error texts. Other browsers word them differently. The first line: no options means duration 0, already finished.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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: noneBreak 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 {}.
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 aCSSTransition: both areAnimations, soel.getAnimations()[0].pause()works.
Remember this
- 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.transformevery frame (Topic 8.1) works but gives up the browser's engine.animate()takes the numbers from JavaScript and keeps the engine. - 2
**What
animate()does.**element.animate(keyframes, options)creates anAnimationobject, puts it on the page'sdocument.timelineand 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
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 carryoffset(0 to 1, where it sits),easing(for the segment after it) andcomposite(Topic 8.5). Property names are camelCase (backgroundColor). - 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. Addoffset: 0and the single keyframe becomes the start instead. - 5
Options, in milliseconds.
duration(a number, default 0),delay,endDelay,iterations(a number orInfinity, where CSS saysinfinite),iterationStart,direction,easing,fill,composite,idandpseudoElement. A bare number as the second argument meansduration. The defaulteasingislinear. - 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 showseffect easing linearandkeyframe easing ease; ananimate()call showslinearfor both unless you set one. So the same 800 ms looks stiffer from JavaScript until you choose an easing (Phase 5). - 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
TypeErrorat 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
**Two things
animate()does not do for you.** It ignoresprefers-reduced-motion: that media query switches CSS only, so checkmatchMediabefore you call it (Topic 7.3). And on adisplay: noneelement it runs (playStateisrunning,getAnimations()lists it) but draws nothing. - 9
**The
Animationobject.** It hasplayState(idle,running,paused,finished),pending(true until the browser has started it, one frame later),effect(the keyframes and timing, withgetKeyframes(),getTiming()andgetComputedTiming()),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 bydocument.getAnimations()until you attach it.
Explain it without notes
What is the difference between a CSS animation and element.animate() when you do not set an easing?
Which mistakes throw an error and which fail silently?
What does a single keyframe mean?
Practice
Write pulse(el) that scales an element to 1.2 and back over 300 ms with ease-out, using one object-form call.
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()