The Web Animations API
element.animate() runs keyframe animations from JavaScript on the compositor — with play, pause, reverse, and a finished promise CSS can't give you.
In one line: the Web Animations API lets you run the same keyframe animations CSS does, but from JavaScript — so you get play, pause, reverse, dynamic values, and a finished promise, while the animation still runs on the browser's compositor.
What it is
CSS keyframes are declarative and fast, but they're hard to control from code: you toggle a class and hope, and coordinating a sequence or reacting to "is it done?" means listening for animationend and bookkeeping.
The Web Animations API (WAAPI) is the same animation engine exposed to JavaScript. element.animate(keyframes, options) starts an animation and hands back an Animation object you can pause(), play(), reverse(), scrub via currentTime, and await through its finished promise. The keyframes are plain objects; the options are duration, easing, delay, iterations, and fill.
Why it matters
It hits a sweet spot between CSS transitions (cheap but rigid) and a full animation library (powerful but a dependency). For dynamic, interruptible motion — a sheet you can fling and catch mid-flight, a sequence that depends on runtime values, an animation you must await before navigating — WAAPI gives you imperative control without shipping a library.
Crucially it stays on the compositor for transform/opacity, so unlike animating via requestAnimationFrame and writing inline styles every frame (which runs on the main thread and competes with your app), WAAPI animations are offloaded and stay smooth under load.
See it
Tweak it2
Hit Play, then change the easing and duration — the generated element.animate() call below updates to match what's running. The spring easing overshoots and settles; linear marches; ease glides.
How it works
animate() takes two arguments:
- Keyframes — an array of style states (
[{ transform: "translateX(0)" }, { transform: "translateX(11rem)" }]), or a single object for an implicit from/to. Stick totransformandopacityfor compositor performance. - Options —
{ duration, easing, delay, iterations, fill }.easingaccepts the same functions as CSS ("ease",cubic-bezier(...),"steps()");fill: "forwards"persists the end state instead of snapping back.
The returned Animation is the control surface. await anim.finished resolves when it ends; anim.reverse() plays it backwards from the current point (great for hover in/out and open/close); anim.cancel() removes it. Because the browser owns the timeline, all of this stays in sync with the compositor.
Build it
type Options = { duration?: number };
export function slideIn(el: HTMLElement, { duration = 320 }: Options = {}): Animation {
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const keyframes: Keyframe[] = reduce
? [{ opacity: 0 }, { opacity: 1 }] // honour the preference: fade only
: [
{ transform: "translateY(12px)", opacity: 0 },
{ transform: "translateY(0)", opacity: 1 },
];
// Returns the Animation so callers can pause / reverse / await it.
return el.animate(keyframes, {
duration: reduce ? 120 : duration,
easing: "cubic-bezier(0.2, 0.7, 0.2, 1)",
fill: "forwards",
});
}
// await it, then do the next thing — no animationend bookkeeping.
async function reveal(el: HTMLElement) {
await slideIn(el).finished;
el.focus();
}Make it yours
Use the controls beside the demo above to change easing and duration — each change updates the example live.
Experiment in the playground- Switch to spring easing and watch the overshoot — that's a
cubic-bezierwhose curve passes above 1 before settling. - Stretch the duration and the same call simply runs longer; the keyframes don't change, only the timing option.
- Picture calling
.reverse()on the returnedAnimationfor the exit — one object drives both directions.
Reproduce it with an LLM
Reproduce it with an LLM
You are a senior front-end engineer. Use the Web Animations API (`element.animate(keyframes, options)`) to build a reusable function that slides and fades a notification element in, holds it, then slides it out — returning the Animation object so the caller can pause, reverse, or await its `finished` promise. Animate only transform and opacity so it stays on the compositor; set `fill: "forwards"` to persist the end state; and read `matchMedia('(prefers-reduced-motion: reduce)')` to fall back to an instant opacity change. Explain in a comment why WAAPI is chosen over CSS keyframes here (dynamic values + JS control). Return only the code with imports.
Pitfalls & accessibility
Related
CSS transitions & keyframes
The two native CSS animation primitives: transitions interpolate a state change, keyframes choreograph a named multi-step sequence. Learn when each one fits.
Choosing an animation tool
CSS, View Transitions, Motion, GSAP, Lottie, Rive — six engines for different jobs. A guide to matching the tool to the animation, starting from boring.