element.animate(): Animations Without a Stylesheet Round-Trip
CSS animations are perfect until the values come from data - an element flying from this clicked position to that cart icon. Suddenly you're injecting style tags or juggling classes and transitionend listeners. The Web Animations API is the missing middle: keyframe semantics, JavaScript values.
const anim = toast.animate(
[
{ opacity: 0, transform: 'translateY(12px)' },
{ opacity: 1, transform: 'translateY(0)' },
],
{ duration: 250, easing: 'ease-out' }
);
await anim.finished; // a real promise - no transitionend bookkeeping
toast.dataset.state = 'shown';
The finished promise alone justifies the API: sequencing animations becomes plain async code, with none of the "did the event fire before I subscribed" races that transitionend loves.
The fill: 'forwards' trap and its fix
Animations revert when they finish - your faded-in toast snaps invisible again. The reflex fix, fill: 'forwards', works but keeps the animation object alive forever holding those styles (they even beat !important, being composited on top). The clean pattern:
const anim = el.animate(keyframes, { duration: 300, fill: 'forwards' });
await anim.finished;
anim.commitStyles(); // write final values into el.style...
anim.cancel(); // ...and release the animation machinery
Where WAAPI beats CSS outright
// Data-driven keyframes - impossible in a stylesheet
card.animate(
[
{ transform: `translate(${fromX}px, ${fromY}px) scale(1)` },
{ transform: 'translate(0, 0) scale(1)' },
],
{ duration: 350, easing: 'cubic-bezier(0.2, 0, 0.2, 1)' }
);
That's the FLIP technique in its natural habitat: measure old position, measure new, animate the delta - list reordering and shared-element effects that feel native. Live playback control is the other differentiator: anim.pause(), reverse(), updatePlaybackRate(2), seeking via currentTime - a menu that smoothly reverses mid-open when the user changes their mind is two method calls.
Sanity rules (same physics as CSS)
- Stick to transform and opacity for 60fps - WAAPI animations of compositable properties run off the main thread, same as CSS.
- Respect prefers-reduced-motion: check the media query and pass duration: 0.
- Static, always-the-same animations still belong in CSS - visible to design tools, cacheable, declarative. WAAPI is for values born at runtime.
Support has been universal for years (the promise and commitStyles bits since ~2020-2021). For whole-page transitions between states, its sibling the View Transitions API picks up where per-element animation ends.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.