wandres.dev
PRINCIPIOS DEL MOVIMIENTO · De la animación clásica a la UI

Anticipación, continuación y solape

Por qué la anticipación se convierte en acuse de recibo en una interfaz, cómo se construye la continuación sin alargar la espera, y qué hace exactamente el solape.

⏱ 18 min

Tres principios de la animación clásica describen lo que ocurre antes y después del movimiento principal: la anticipación lo prepara, la continuación lo remata y el solape hace que las partes de un conjunto no se muevan al unísono. Los tres tienen traducción a interfaces, pero solo uno traslada sin cambios. La anticipación se convierte en algo distinto, la continuación tiene un límite duro que en cine no existe, y el solape es probablemente la técnica con mejor relación entre esfuerzo y efecto de todo este nivel.

🎯 Al terminar esta lección sabrás
  • Explicar por qué la anticipación clásica añade latencia percibida y qué la sustituye.
  • Construir un acuse de recibo que reduzca la latencia percibida en lugar de aumentarla.
  • Aplicar continuación sin alargar el tiempo total del movimiento.
  • Calcular un escalonado que comunique cardinalidad sin convertirse en espera.

La anticipación se convierte en acuse de recibo

En animación clásica, la anticipación es un movimiento contrario al principal que lo precede: el bateador echa el bate hacia atrás, el personaje se agacha antes de saltar. Cumple dos funciones. Prepara físicamente el movimiento, y avisa al espectador de que va a pasar algo, dándole tiempo a mover la mirada.

La segunda función es la interesante y es la que no traslada. En una interfaz el usuario ya sabe que va a pasar algo, porque lo ha provocado él. Avisarle es redundante, y el aviso cuesta tiempo: cualquier movimiento previo al principal se suma a la latencia entre la acción y el resultado.

Eso deja la anticipación clásica sin razón de ser en el destino del movimiento. Pero la necesidad que resolvía —cerrar el bucle entre acción y consecuencia— sigue existiendo, y en una interfaz se resuelve en el otro extremo: en el origen, y de forma inmediata.

La diferencia es fundamental. Un panel que se encoge antes de abrirse añade latencia. Un botón que responde en el momento de la pulsación resta latencia percibida, porque el usuario obtiene confirmación de que su acción llegó antes de que el resultado esté listo. Es el mismo principio que hace que un ascensor con el botón iluminado se perciba como más rápido que uno igual sin luz.

.boton {
  scale: 1;
  transition: scale 140ms cubic-bezier(0.2, 0, 0, 1);
}

/* Respuesta inmediata a la pulsacion: baja rapido, sube suave. */
.boton:active {
  scale: 0.97;
  transition-duration: 40ms;
}

Los dos números importan y no son intercambiables. La bajada tiene que ser casi instantánea, porque su trabajo es acusar recibo y cualquier retardo ahí es latencia. La subida puede permitirse tres veces más, porque ya no informa de nada urgente y una vuelta brusca se lee como un rebote.

Hay un caso donde la anticipación clásica sí traslada, y conviene reconocerlo: cuando el movimiento no lo ha provocado el usuario. Una notificación que va a aparecer, un elemento que el sistema va a mover por su cuenta. Ahí el usuario no sabe que va a pasar algo, y un movimiento preparatorio mínimo cumple su función original de dirigir la mirada antes del evento.

La continuación tiene un límite que en cine no existe

La continuación es lo que sigue moviéndose después de que el movimiento principal termine: la capa cuando el personaje se para, el pelo, la punta del látigo. Comunica que el objeto tiene partes con inercia propia.

En una interfaz traslada, pero con una restricción que no existe en cine: el tiempo total percibido no puede crecer. En una película, alargar el plano cien milisegundos no le cuesta nada a nadie. En una interfaz, el usuario está esperando a que la operación acabe para poder actuar, y la continuación se percibe como parte de la espera.

La solución es que la continuación ocurra dentro del tiempo del movimiento principal, no después. Se consigue desplazando el momento en que cada parte llega a su valor final en lugar de alargar el conjunto:

/* La tarjeta llega en 260ms. Su sombra y su contenido llegan
   un poco despues, pero dentro de la misma ventana total. */
.tarjeta {
  transition:
    translate 260ms cubic-bezier(0.2, 0, 0, 1),
    box-shadow 260ms cubic-bezier(0.2, 0, 0, 1) 40ms,
    opacity 200ms linear 60ms;
}

La ventana total sigue siendo de 260 milisegundos para el desplazamiento, que es lo que el usuario cronometra. La sombra tarda 300 en total pero es una propiedad que nadie está esperando, y su retraso es lo que produce la sensación de que la tarjeta llegó y su sombra la alcanzó después.

Otra forma de conseguir continuación sin coste temporal es el sobrepaso, que ya no es un retraso sino una curva: el elemento llega, se pasa ligeramente y vuelve. La clave es la magnitud. Un sobrepaso de más de un dos o tres por ciento del recorrido deja de leerse como inercia y se lee como rebote decorativo:

.entra {
  /* El componente vertical del segundo punto de control supera 1:
     el progreso pasa de 1 y vuelve. */
  transition: translate 280ms cubic-bezier(0.34, 1.16, 0.64, 1);
}

Compara ese 1.16 con el 1.56 que aparece en los generadores de curvas con rebote. La diferencia entre inercia y espectáculo cabe en un decimal.

El solape se percibe por el hueco, no por el retardo

El escalonado es la técnica de este nivel que más gente aplica y peor calibra, y la razón es que se piensa en él como “un retardo entre elementos” cuando lo que el sistema visual mide es el hueco entre el final de uno y el principio del siguiente. Con una duración de 300 milisegundos por elemento y un retardo de 50, los elementos se solapan casi por completo: el segundo arranca cuando el primero lleva un sexto de camino, y el conjunto se lee como una onda continua. Con la misma duración y un retardo de 350, no hay solape en absoluto: cada elemento espera a que el anterior termine, y el conjunto se lee como una cola. El primero comunica que las cosas están relacionadas; el segundo comunica que hay que esperar. Y aquí está la trampa aritmética que arruina las listas largas: el tiempo total crece con el número de elementos, así que un escalonado calibrado con cinco elementos se convierte en una espera insoportable con cuarenta. Un retardo de 60 milisegundos que en cinco elementos suma 240 de cola, en cuarenta suma 2340, y a nadie se le ocurre que el problema esté en el escalonado porque cada elemento individual sigue durando lo mismo. La forma correcta de escalonar listas de tamaño desconocido es fijar la ventana total y repartir el retardo dentro de ella, no fijar el retardo por elemento: decides que todo el conjunto entra en 400 milisegundos y divides. Con cinco elementos el retardo sale de 80 y con cuarenta de 10, y en los dos casos la cola termina cuando dijiste. Y por encima de un número —en la práctica, alrededor de diez— deja de tener sentido escalonar cada elemento: escalona grupos, porque el sistema visual tampoco distingue el elemento veintitrés del veinticuatro.

Escalonar con la ventana fija

La implementación del criterio anterior es corta y se puede hacer entera en CSS moderno, sin JavaScript, contando los hermanos con sibling-index() y sibling-count() donde estén disponibles, o pasando el índice como custom property cuando no.

La versión portable, con el índice inyectado una sola vez:

const filas = [...lista.children];
const VENTANA = 400; // ms para todo el conjunto, sea cual sea el tamano
const paso = filas.length > 1 ? VENTANA / (filas.length - 1) : 0;

filas.forEach((fila, i) => {
  fila.style.setProperty('--retardo', `${Math.round(i * paso)}ms`);
});
.fila {
  opacity: 0;
  translate: 0 8px;
  animation: entrar 220ms cubic-bezier(0.2, 0, 0, 1) var(--retardo, 0ms) both;
}

@keyframes entrar {
  to { opacity: 1; translate: 0 0; }
}

Con both como modo de relleno, cada fila mantiene el estado inicial durante su retardo y el final después, que es lo que evita el parpadeo de las filas que aún no han empezado.

El mismo cálculo aplicado a grupos, para listas largas: en lugar de dividir por el número de filas, agrupas de cinco en cinco y usas el índice de grupo. Visualmente el resultado es indistinguible a partir de cierto tamaño, y el tiempo total deja de depender del contenido.

Un último detalle que se olvida siempre: el escalonado tiene dirección, y la dirección comunica. Un escalonado que va de arriba abajo se lee como que el contenido baja; de abajo arriba, como que sube. En un menú que se despliega hacia abajo, escalonar de abajo arriba contradice el movimiento del contenedor y produce una incoherencia que se nota sin saber por qué. La dirección del escalonado debe coincidir con la dirección desde la que llega el conjunto.

⚔️ Calibra un escalonado por el hueco
  1. Monta una lista de ocho elementos con una animación de entrada de 220 milisegundos y un retardo por elemento de 40. Anota cómo se lee.
  2. Sube el retardo a 240 sin tocar la duración. Anota cómo se lee ahora y en qué punto exacto dejó de ser una onda y pasó a ser una cola.
  3. Cambia la lista a cuarenta elementos sin tocar nada y cronometra el tiempo total. Reescríbelo con ventana fija y comprueba que el total no cambia.