Web Animations (WAAPI + CSS Transitions + CSS Animations)
Guia de referencia para las 3 APIs nativas de animacion del browser. Complementa la skill view-transitions (navegacion entre estados/paginas — ver ../view-transitions/SKILL.md).
Cuando Usar Cada API
| Necesidad | API | Por que |
|-----------|-----|---------|
| Hover, focus, estado simple | CSS Transitions | Declarativo, cero JS |
| Secuencia de keyframes pura CSS | CSS Animations | @keyframes + animation |
| Control runtime (pause, reverse, seek) | WAAPI | Element.animate() retorna Animation controlable |
| Scroll-driven reveal/parallax | CSS animation-timeline o WAAPI ScrollTimeline | Off main thread, sin jank |
| Entry desde display:none | CSS Transitions + @starting-style + allow-discrete | Nativo, sin JS |
| Animacion de height: auto | CSS + interpolate-size | Chrome 129+, progressive enhancement |
| Logica per-frame (fisica, canvas) | requestAnimationFrame | Ultimo recurso, main thread only |
Web Animations API (WAAPI)
Element.animate()
const anim = element.animate(
[{ transform: 'translateX(0)', opacity: 1 }, { transform: 'translateX(300px)', opacity: 0 }],
{
duration: 300, // milisegundos (NO segundos como CSS)
easing: 'ease-out', // default es 'linear' (distinto a CSS!)
iterations: 1, // Infinity para loops
fill: 'forwards', // none | forwards | backwards | both
direction: 'alternate', // normal | reverse | alternate | alternate-reverse
delay: 100,
composite: 'replace', // replace | add | accumulate
}
);
Keyframes: array de objetos (flexible, soporta offset/easing por keyframe) u objeto compacto ({ opacity: [0, 1] }). Implicit from/to: element.animate({ transform: 'translateX(300px)' }, 400) anima desde el valor actual. Siempre camelCase (backgroundColor).
El Objeto Animation
// Playback
anim.play(); anim.pause(); anim.reverse(); anim.finish(); anim.cancel();
// Estado
anim.playState; // 'idle' | 'running' | 'paused' | 'finished'
anim.currentTime; // ms | null
anim.playbackRate; // 1.0 default, negativo = reversa
anim.updatePlaybackRate(0.5); // smooth rate change
// Promises (async/await)
await anim.ready; // animacion arranco
await anim.finished; // animacion termino
// Events: onfinish, oncancel, onremove
Persistir Estilos (Preferir sobre fill:'forwards')
anim.finished.then(() => { anim.commitStyles(); anim.cancel(); });
fill: 'forwards' mantiene memoria y tiene side-effects en la cascada. commitStyles() escribe los valores computados en element.style y libera recursos.
getAnimations()
element.getAnimations(); // del elemento (incluye CSS animations/transitions)
document.getAnimations(); // todas del documento
Para features avanzadas (KeyframeEffect constructor, compositing, pseudo-element targeting), leer references/waapi-advanced.md.
CSS Transitions
transition: opacity 200ms ease-out, transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
@starting-style — Entry Animations
Sin @starting-style, las transitions no se activan en el primer render (no hay estado "antes"):
.card {
opacity: 1; transform: translateY(0);
transition: opacity 300ms, transform 300ms;
@starting-style { opacity: 0; transform: translateY(8px); }
}
Animar display con allow-discrete
.dialog {
display: none; opacity: 0;
transition: opacity 200ms ease, display 200ms allow-discrete;
}
.dialog[open] { display: block; opacity: 1; }
@starting-style { .dialog[open] { opacity: 0; } }
Eventos: transitionend, transitionrun, transitionstart, transitioncancel. Gotcha: transitionend NO se dispara si se interrumpe.
CSS Animations
@keyframes slide-in {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.element {
animation: slide-in 300ms ease-out forwards;
/* name | duration | easing | fill-mode */
/* Composicion con otras animaciones */
animation-composition: add; /* replace | add | accumulate */
}
Eventos
element.addEventListener('animationend', (e: AnimationEvent) => {
e.animationName; // nombre del @keyframes
});
// Tambien: animationstart, animationcancel, animationiteration
Performance
Propiedades compositor (GPU, preferir siempre): transform, opacity, filter, clip-path
Propiedades main thread (evitar animar): width, height, top, left, background-color, box-shadow
CSS Animations y WAAPI corren en compositor para las propiedades GPU. requestAnimationFrame siempre corre en main thread.
will-change: aplicar solo cuando la animacion es inminente (ej: .card:hover { will-change: transform; }). No aplicar a todo — cada capa promovida consume GPU memory.
Layout thrashing: no intercalar reads (offsetHeight) y writes (style.height) en un loop. Batch reads primero, luego writes.
Scroll-Driven Animations
Para scroll-driven animations (ScrollTimeline, ViewTimeline, animation-timeline, animation-range), leer references/scroll-driven.md.
Patrones Modernos
Para interpolate-size, prefers-reduced-motion, @supports guards y otros patrones modernos, leer references/patterns.md.
Gotchas
- WAAPI default easing es
linear, noeasecomo CSS — siempre especificar - WAAPI duration en milisegundos, CSS en segundos —
duration: 300= 300ms fill: 'forwards'tiene side-effects — preferircommitStyles()+cancel()- Transitions no se activan sin estado previo — usar
@starting-styleo rAF delay transitionendno se dispara si se interrumpe — no depender como unico cleanup- Compositor vs main thread — animar solo
transform,opacity,filter,clip-path will-changeno es gratis — cada capa consume GPU memoryanimation-duration: autorequerido para scroll-driven timelines en CSS
Browser Support (2025)
| Feature | Chrome | Edge | Firefox | Safari |
|---------|--------|------|---------|--------|
| Element.animate() core | 36+ | 79+ | 48+ | 13.1+ |
| commitStyles() / persist() | 84+ | 84+ | 75+ | 13.1+ |
| @starting-style | 117+ | 117+ | 129+ | 17.5+ |
| transition-behavior: allow-discrete | 117+ | 117+ | 129+ | 17.5+ |
| Scroll-driven (CSS) | 115+ | 115+ | flag | 18+ (parcial) |
| ScrollTimeline / ViewTimeline JS | 115+ | 115+ | flag | 18+ (parcial) |
| interpolate-size | 129+ | 129+ | No | No |
Checklist Pre-Deploy
[ ] prefers-reduced-motion implementado
[ ] Solo animar propiedades de compositor (transform, opacity, filter)
[ ] will-change aplicado selectivamente y removido post-animacion
[ ] Duraciones razonables (150-400ms transiciones, hasta 1s animaciones complejas)
[ ] @starting-style para entry animations
[ ] commitStyles() + cancel() en vez de fill:'forwards' persistente
[ ] Scroll-driven animations con @supports guard y fallback visible
[ ] Testeado en Chrome, Safari Y Firefox