Las cuatro familias y qué resuelve cada una
CSS declarativo, WAAPI imperativo, líneas de tiempo de scroll y librerías: el problema concreto que resuelve cada familia y el que no puede resolver.
Hay cuatro maneras de animar en la web y la mayoría de los debates sobre cuál es mejor son debates mal planteados, porque comparan herramientas que responden a preguntas distintas. Una animación no se define solo por lo que se mueve: se define por quién decide cuándo empieza y quién decide cuándo acaba. Esa pregunta, y no el rendimiento ni la sintaxis, es la que reparte el territorio entre las cuatro familias.
- Enunciar el problema específico que resuelve cada una de las cuatro familias.
- Identificar el límite duro de CSS declarativo y por qué existe.
- Explicar qué añade una línea de tiempo de scroll que no es azúcar sintáctico.
- Justificar en qué casos una librería aporta algo que las APIs nativas no dan.
Familia 1: CSS declarativo
transition y @keyframes comparten una característica que las define: el disparador es un cambio de estilo. Tú no dices “anima ahora”; dices “cuando el estilo computado de este elemento cambie de esta manera, interpola en vez de saltar”. El navegador se encarga del resto.
Eso resuelve un problema muy concreto y muy frecuente: la mayoría de las animaciones de interfaz son consecuencia de un cambio de estado que ya estabas expresando en CSS de todos modos. El elemento está enfocado o no, el menú está abierto o no, la fila está seleccionada o no. Si ese estado ya vive en una clase o en un pseudo-selector, la animación es una línea más en la misma regla y no necesita coordinación con nada.
La ventaja real no es la brevedad. Es que no hay estado duplicado. Con CSS declarativo no existe la posibilidad de que la animación y la interfaz se desincronicen, porque la animación no tiene estado propio: es una función del estilo computado. Cuando el estado cambia dos veces rápido, el motor resuelve la interrupción por ti partiendo del valor actual, no del valor final anterior.
.panel {
translate: 0 0;
opacity: 1;
transition: translate 220ms ease-out, opacity 220ms ease-out;
}
.panel[hidden-anim] {
translate: 0 -8px;
opacity: 0;
}
El límite duro de esta familia es igual de claro: CSS no sabe secuenciar. No hay forma de decir “cuando termine esta, empieza aquella” sin recurrir a retrasos calculados a mano, que se rompen en cuanto cambias una duración. Tampoco sabe medir: no puede leer la altura de un elemento para decidir cuánto desplazarlo. Ni puede reaccionar a nada que no sea un cambio de estilo. Esos tres huecos son exactamente lo que llenan las otras familias.
Familia 2: WAAPI imperativo
element.animate() y el objeto Animation que devuelve son la misma maquinaria de CSS expuesta como API. Lo que aportan no es potencia de interpolación —es idéntica— sino control temporal y capacidad de composición.
Una animación WAAPI se puede pausar, invertir, acelerar, reposicionar en el tiempo y esperar. La promesa finished convierte el encadenamiento en algo trivial y correcto, en lugar de una suma frágil de retrasos:
async function abrirFicha(ficha, filas) {
await ficha.animate(
[{ opacity: 0, scale: 0.96 }, { opacity: 1, scale: 1 }],
{ duration: 180, easing: 'ease-out', fill: 'forwards' }
).finished;
// Solo despues de que la ficha exista visualmente, entran las filas.
filas.forEach((fila, i) => {
fila.animate(
[{ opacity: 0, translate: '0 6px' }, { opacity: 1, translate: '0 0' }],
{ duration: 160, delay: i * 40, easing: 'ease-out', fill: 'backwards' }
);
});
}
El segundo aporte es la medición. Como estás en JavaScript, puedes leer geometría real y construir los fotogramas con ella. Esa es la única forma de animar un valor que depende del layout, y es la base de técnicas como FLIP.
El tercero, menos conocido, es que WAAPI puede crear animaciones sin tocar la hoja de estilos ni el atributo de estilo. Una animación creada así no ensucia el estilo en línea del elemento, no compite con la cascada y se limpia sola al cancelarse. Para animaciones efímeras —un destello de confirmación, un temblor de error— es notablemente más limpio que añadir y quitar clases.
Su coste es simétrico al de CSS: el estado vuelve a ser tuyo. Si el usuario cierra el panel mientras la animación de apertura corre, tienes que cancelarla o esperarla. Nadie lo hace por ti.
Familia 3: líneas de tiempo que no son el reloj
Esta familia no cambia lo que se anima, cambia de dónde viene el tiempo. Y ese cambio, que suena menor, elimina una categoría entera de código.
Antes, atar una animación al scroll significaba escuchar el evento de scroll, calcular una fracción de progreso, y escribir estilos a mano en cada disparo. Eso tiene tres problemas que no se pueden arreglar del todo: el evento se dispara en el hilo principal, así que un hilo ocupado produce saltos; el cálculo de progreso obliga a leer geometría, así que introduce lecturas forzadas de layout; y el resultado nunca está perfectamente sincronizado con el desplazamiento real, porque el scroll puede ocurrir en el compositor mientras tu código todavía no se ha ejecutado.
Con animation-timeline: scroll() o view(), la animación declara que su fuente de tiempo es una posición de scroll, y el motor la evalúa donde corresponde. No hay escucha de eventos, no hay lectura de geometría desde JavaScript y no hay desincronización posible.
@keyframes progreso { from { scale: 0 1; } to { scale: 1 1; } }
.barra-progreso {
transform-origin: left center;
animation: progreso linear;
animation-timeline: scroll(root block);
}
El precio, en 2026, es de compatibilidad: Chromium y Safari lo implementan, Firefox no. Es una familia que se adopta como mejora progresiva, detectando con @supports (animation-timeline: scroll()) y dejando un camino base que funcione sin ella. Que sea puramente decorativa en la mayoría de los usos hace que ese camino base sea a menudo “no hacer nada”.
El mismo cambio de idea, aplicado a otra cosa, produce las transiciones de vista: ahí la línea de tiempo sigue siendo el reloj, pero lo que cambia es quién construye los fotogramas. Los construye el navegador, capturando el antes y el después del DOM. Sigue siendo el mismo modelo de tres objetos.
Todo el mundo elige familia por rendimiento y casi siempre es el criterio equivocado, porque las cuatro terminan en el mismo interpolador y en el mismo pipeline: una animación de transform es igual de barata la escribas como la escribas. El criterio que de verdad discrimina es dónde vive el estado de la animación. En CSS declarativo el estado es el estilo, no hay copia; en WAAPI el estado vive en un objeto Animation que tú posees y tienes que gestionar; en una línea de tiempo de scroll el estado vive en el scroller y nadie lo posee; en una librería vive en el grafo interno de la librería. La consecuencia práctica llega el día que dos animaciones se pisan. Si el estado es el estilo, la interrupción se resuelve sola y correctamente. Si el estado está duplicado en un objeto o en un grafo externo, la interrupción es un problema tuyo, y el síntoma clásico es la animación que se queda a medias o que salta al valor final al empezar la siguiente. Por eso la heurística que funciona no es “usa CSS porque es más rápido”, es usa la familia con menos estado propio que resuelva el problema, y sube de familia solo cuando el problema te lo exija. Cada escalón que subes es un lugar más donde la interfaz y su representación animada se pueden desincronizar.
Familia 4: librerías
La pregunta correcta sobre una librería de animación no es si es rápida, sino qué problema resuelve que las tres familias anteriores no resuelven. Hay cuatro respuestas legítimas y conviene reconocerlas.
Secuenciación compleja. Una línea de tiempo con veinte pasos, solapes relativos, etiquetas y control global sobre el conjunto es tediosa de construir a mano con promesas y prácticamente imposible de reproducir hacia atrás. Este es el argumento más sólido y el que justifica la mayoría de las adopciones.
Interpolación de cosas que el navegador no interpola. Morfear un trazado SVG en otro con distinto número de puntos, dividir un texto en líneas respetando el reflujo, animar a lo largo de una curva con orientación tangente. Nada de esto sale gratis con APIs nativas.
Física con continuidad. Un muelle real conserva la velocidad cuando lo interrumpes. Las funciones de suavizado no: son tablas fijas, y si interrumpes una empiezas otra desde velocidad cero. Cuando el movimiento debe responder a un gesto continuo, esa diferencia se nota.
Cobertura uniforme. Una librería normaliza el comportamiento entre motores, incluidos los que no han implementado la capacidad reciente que quieres usar.
Fuera de esas cuatro, una librería es peso muerto. Y conviene saber que el argumento económico que durante años pesó en esta decisión ya no existe: GSAP es gratuito desde abril de 2025, tras la adquisición por Webflow, plugins incluidos y uso comercial cubierto. La elección volvió a ser puramente técnica.
- Coge tres animaciones de un proyecto real y anota, para cada una, quién decide cuándo empieza y quién decide cuándo acaba.
- Asigna cada una a su familia según la heurística del estado, no según cómo esté implementada hoy.
- Si alguna está implementada en una familia superior a la que le corresponde, escribe en una frase qué se ganó y qué se pagó con ese salto.