web-animations-api-native
Web Animations API (native, no library)
When to use this
- The project has a hard constraint against adding an animation dependency, or the animation need is simple enough that a library's abstraction cost is not worth it.
- You need low-level control that libraries partially abstract away:
playbackRatemanipulation,compositemodes for additive animation, precisefillbehavior, or synchronizing multiple animations against a sharedDocumentTimelineorScrollTimeline. - You're building a design-system primitive or a library yourself and want the animation layer to be dependency-free.
- You need native CSS
@keyframes-equivalent behavior but constructed/controlled from JS at runtime (dynamic keyframe values you cannot know at CSS-authoring time). - Do NOT use this when you need cross-browser spring physics, complex sequencing with labels/overlap syntax, or SVG morphing; hand-rolling those on raw WAAPI is significant, error-prone work that
gsap-core-timelines,motion-one-waapi, orreact-spring-physicsalready solve well.
Mental model
The Web Animations API is the browser's own animation engine exposed to JavaScript, the same engine that runs CSS @keyframes and CSS transitions under the hood. Calling element.animate(keyframes, options) constructs a KeyframeEffect (the "what changes and how," bundling the target element, the keyframe list, and timing options) and immediately associates it with an Animation object (the "playback controller," analogous to a <video> element's play head) and starts it playing on the document's default DocumentTimeline, which is essentially a clock that ticks forward with real time since page load.
Because this runs through the same engine as CSS animations, the browser can run the interpolation on the compositor thread whenever the animated properties are compositor-safe (transform, opacity, filter in modern engines), independent of main-thread JavaScript execution. This is the same underlying mechanism motion-one-waapi builds convenience APIs on top of; using it directly means you get identical runtime performance with zero library code, at the cost of writing more verbose setup and sequencing logic yourself.
An Animation object's lifecycle state matters: it has a playState ('idle', 'running', 'paused', 'finished'), a currentTime (can be read and SET directly, which is how scroll-scrubbing or manual scrubbing is implemented without a library), and a playbackRate (can be negative to run in reverse, or fractional to slow down/speed up live). fill controls what happens to the target's styling outside the animation's active interval: 'forwards' keeps the last keyframe's computed style applied after the animation finishes (without this, WAAPI animations by default do NOT persist their effect, and the element visually snaps back to its pre-animation style the instant the animation ends), 'backwards' applies the first keyframe's style during any delay before it starts, and 'both' combines the two.
composite and iterationComposite control how an animation's effect combines with the underlying base value or with itself across iterations, rather than always fully replacing the value. composite: 'add' adds the keyframe's computed value to whatever the property's current value already is (useful for layering an animation on top of another without one overwriting the other, e.g. a shake effect additively layered on top of a drag-driven transform), while the default 'replace' simply sets the value outright each frame.
document.timeline is the default DocumentTimeline every element.animate() call uses unless you specify otherwise; browsers implementing ScrollTimeline/ViewTimeline (the CSS scroll-driven-animations spec) let you construct a new ScrollTimeline({...}) and assign it as an Animation's .timeline property, making a native animation scroll-driven with zero JavaScript executing per scroll event, the most efficient possible form of scroll-linked animation where supported.