Qué es una timeline en el modelo de animación web
La pieza que produce el tiempo, separada de la que lo traduce y de la que produce los valores. Tres funciones compuestas, y la de dentro es intercambiable.
Durante veinte años la web tuvo una sola fuente de tiempo para las animaciones y por eso nadie se preguntó qué era. Ahora hay más de una, y el modelo que siempre estuvo ahí se vuelve visible: una animación no tiene un reloj, lee uno. La separación entre quien produce el tiempo, quien lo traduce a coordenadas de la animación y quien convierte esa coordenada en valores es lo que hace posible sustituir la primera pieza sin tocar las otras dos. Este nivel es sobre esa separación, y sobre lo que se puede hacer una vez la ves.
- Describir las tres funciones que componen una animación y qué produce cada una.
- Explicar qué es una
AnimationTimeliney cuál es su único miembro relevante. - Razonar qué le ocurre a una animación sin línea de tiempo.
- Reconocer que
anim.timelinees escribible y qué significa eso.
Tres funciones compuestas
flowchart LR T[AnimationTimeline produce un tiempo] --> A[Animation lo traduce a tiempo local] A --> E[AnimationEffect lo traduce a progreso y valores] E --> D[elemento del DOM] style T fill:#cba6f7,color:#11111b style A fill:#89b4fa,color:#11111b style E fill:#94e2d5,color:#11111b style D fill:#a6e3a1,color:#11111b
La cadena tiene tres eslabones y cada uno sabe muy poco de los demás.
La línea de tiempo produce un valor y nada más. No sabe qué animaciones cuelgan de ella, ni cuántas, ni qué hacen. Su interfaz es esencialmente una propiedad de lectura: currentTime.
La animación traduce ese valor a su propio sistema de coordenadas aplicando el ancla y la velocidad, que es la ecuación que ya conoces. Además gestiona el estado de reproducción. No sabe qué propiedades se están animando ni sobre qué elemento.
El efecto recibe un tiempo local, lo convierte en un progreso aplicando la duración, las iteraciones, la dirección y la curva, y a partir de ese progreso interpola los valores. No sabe de dónde viene el tiempo ni si el reloj avanza solo o lo mueve alguien.
Esta última ignorancia es la clave de todo. El KeyframeEffect es una función pura de progreso a valores. Le da exactamente igual si el progreso 0.5 llega porque han pasado 500 de 1000 milisegundos o por cualquier otro motivo. Toda la maquinaria de keyframes, offsets, easing por tramo, composición y relleno que has aprendido en los niveles anteriores funciona idénticamente sea cual sea la fuente.
La interfaz de una línea de tiempo
AnimationTimeline es una interfaz deliberadamente mínima. Lo único que hace falta para ser una línea de tiempo es exponer un tiempo actual:
document.timeline; // una DocumentTimeline
document.timeline instanceof AnimationTimeline; // true
document.timeline.currentTime; // milisegundos desde el origen del documento
DocumentTimeline es la única implementación que la plataforma ha tenido durante casi toda su historia, hasta el punto de que la mayoría de la documentación habla de “el tiempo” como si fuera un concepto sin alternativa. Su contrato es sencillo: empieza en cero cuando el documento define su origen temporal y avanza con el reloj del sistema.
Una línea de tiempo puede estar inactiva, y en ese caso su currentTime vale null. Para la del documento eso solo ocurre antes de que el documento haya pintado su primer frame. Para otras clases de línea de tiempo, la inactividad es un estado normal y recurrente, y por eso el modelo la contempla desde el principio en vez de tratarla como un error.
Una animación sin línea de tiempo
Se puede construir una animación sin fuente de tiempo, y el resultado enseña bien cuál es la responsabilidad de cada pieza:
const efecto = new KeyframeEffect(el, { opacity: [0, 1] }, { duration: 500 });
const anim = new Animation(efecto, null);
anim.play();
anim.playState; // 'running'
anim.currentTime; // null
La animación está en estado 'running' y no ocurre absolutamente nada. No hay tiempo, así que no hay tiempo local, así que no hay progreso, así que el efecto no produce ningún valor. El elemento muestra su estilo de cascada.
En cuanto le das una fuente, arranca:
anim.timeline = document.timeline;
Ese asignador es el punto interesante. timeline es una propiedad escribible, y cambiarla en una animación en marcha la reconecta a otra fuente de tiempo conservando su efecto, su estado y su configuración. Es la operación que hace que la sustitución de la que habla este nivel sea literal: una animación puede dejar de avanzar con el reloj y pasar a avanzar con otra cosa cambiando una propiedad.
Lo que no puede hacer es tener dos. Una animación tiene exactamente una línea de tiempo o ninguna.
Lo que el modelo asumía sin decirlo
Que solo hubiera una implementación durante veinte años dejó supuestos implícitos por todo el modelo, y ahora que hay alternativas conviene sacarlos a la luz porque son justo los puntos donde algo se comporta distinto.
El tiempo siempre avanza. Ningún código que consuma animaciones estaba preparado para que currentTime disminuyera por sí solo, sin que nadie asignara nada.
El tiempo no tiene fin. iterations: Infinity tiene sentido porque el reloj no se acaba. Una duración se expresa en milisegundos porque el milisegundo es una unidad absoluta que no depende de nada.
Terminar es definitivo. La promesa finished se resuelve una vez, el evento finish se dispara una vez por ciclo, y el paso a estado 'finished' es la culminación de un proceso irreversible salvo que alguien intervenga.
Ninguno de los tres es cierto para todas las líneas de tiempo. Y el modelo no se rompe por eso, porque las tres piezas estaban ya bien separadas: lo único que cambia es lo que produce la primera.
La propiedad más elegante de este diseño es que no hubo que cambiar nada del efecto para admitir fuentes de tiempo nuevas. Ni una palabra de la especificación de keyframes, ni del easing, ni del relleno, ni de la composición. Toda la complejidad que se acumuló en veinte años de animación CSS —los keyframes implícitos, el easing por tramo, las fases previa y posterior, add y accumulate— sigue valiendo tal cual, y sirve exactamente igual para una animación dirigida por el desplazamiento que para una dirigida por el reloj. Eso no es casualidad ni suerte: es el resultado de que las especificaciones de Web Animations definieran el efecto en términos de progreso y no de tiempo desde el primer día, en 2014, sin que hubiera ninguna necesidad práctica de hacerlo. Es uno de los pocos casos en la historia de la plataforma web en que una abstracción que parecía burocracia de especificación pagó por completo diez años después. La lección práctica para leer especificaciones: cuando un documento define una capa intermedia que parece innecesaria, casi siempre es que alguien está reservando sitio para algo que aún no existe.
Lo que sigue
Con la separación clara, la pregunta del nivel se puede formular con precisión: ¿qué otra cosa puede producir un tiempo? La respuesta que ha adoptado la plataforma es cualquier magnitud que tenga un principio, un final y una posición actual entre ambos. Un recorrido de desplazamiento tiene esas tres cosas. Y con eso, el 0% y el 100% de una regla @keyframes dejan de significar “el principio y el final del tiempo” para significar “el principio y el final del recorrido”.
Antes de llegar ahí hace falta precisar en qué se diferencia una fuente de progreso de una fuente de tiempo, porque no es solo la unidad: es la monotonía, la acotación y la reversibilidad. Eso es lo siguiente.
Construye una animación con new Animation(efecto, null) y comprueba que play() la deja en 'running' con currentTime a null y sin efecto visible. Asígnale después document.timeline y observa que arranca desde el principio. Por último, crea una new DocumentTimeline({ originTime: 3000 }), asígnasela a una animación que ya estaba corriendo y mira cómo salta su currentTime.