wandres.dev
COREOGRAFÍA · Sistemas de movimiento

Distancia, tamaño y la escala de duraciones derivada

Por qué la duración no es proporcional a la distancia sino sublineal, la fórmula con exponente y su tabla, cómo influye el tamaño del elemento, y una implementación que ajusta la duración midiendo el recorrido real.

⏱ 19 min

Una hoja lateral que recorre trescientos píxeles y un modal que recorre veinte no pueden durar lo mismo, y tampoco pueden durar en proporción a la distancia. Si aplicas la misma velocidad a los dos, el corto parece un parpadeo y el largo se hace eterno; si aplicas la misma duración, el largo se percibe como un latigazo. La relación correcta entre distancia y duración es sublineal, y saber eso convierte la escala de duraciones en algo que se puede derivar en lugar de adivinar.

🎯 Al terminar esta lección sabrás
  • Explicar por qué la duración crece más despacio que la distancia.
  • Aplicar la fórmula con exponente y traducirla a la tabla que usará el equipo.
  • Ajustar la duración según el tamaño del elemento y la proporción de pantalla que ocupa.
  • Implementar el cálculo midiendo el recorrido real en tiempo de ejecución.

Ni velocidad constante ni duración constante

Los dos modelos ingenuos fallan de forma simétrica.

Duración constante significa que la velocidad crece con la distancia. Un elemento que recorre veinte píxeles en doscientos cincuenta milisegundos va a ochenta píxeles por segundo; el mismo tiempo para ochocientos píxeles son tres mil doscientos píxeles por segundo. El segundo se percibe como un movimiento violento, y en pantallas grandes produce además un rastro visual desagradable.

Velocidad constante significa que la duración crece proporcionalmente. Si veinte píxeles tardan cien milisegundos, ochocientos tardan cuatro segundos. Insostenible: nadie va a esperar cuatro segundos a que se abra un panel.

Lo que ocurre en realidad es que la velocidad que se percibe como natural crece con la distancia, pero más despacio que ella. Es la misma familia de relaciones que gobierna casi toda la percepción: un movimiento largo puede ir mucho más rápido sin parecer brusco, porque el ojo dispone de más recorrido para seguirlo y porque la referencia perceptiva es la fracción de pantalla recorrida, no los píxeles absolutos.

La forma funcional que mejor se ajusta en la práctica es una potencia con exponente cercano a un medio:

duracion = duracion_base × (distancia / distancia_base) ^ 0.5

Con una base de 250 milisegundos para 200 píxeles, cuadruplicar la distancia solo duplica la duración. Eso reparte los valores de una forma que se corresponde bien con lo que la gente elige a ojo cuando se le deja iterar, que es la mejor validación disponible para una regla perceptiva.

La tabla

La fórmula es la justificación; lo que usa un equipo es una tabla, porque nadie va a calcular raíces cuadradas en una revisión de diseño.

Recorrido Duración Token más cercano Ejemplo
Menos de 40 px 100 ms --mov-micro Hundido de un botón, anillo de foco
40 a 150 px 150 ms --mov-corta Menú desplegable, tooltip, chip
150 a 400 px 250 ms --mov-media Panel lateral estrecho, modal, tarjeta
400 a 800 px 320 ms Entre media y larga Hoja lateral ancha, expansión a detalle
Más de 800 px 400 ms --mov-larga Transición de pantalla, hoja a pantalla completa

Fíjate en que la tabla se aplana por arriba: entre ochocientos y dos mil píxeles la duración apenas cambia. Es consecuencia directa del exponente, y también de que a partir de cierto punto lo que domina la percepción ya no es la distancia sino la fracción de la pantalla que se está moviendo.

Y en pantallas muy anchas hay una corrección adicional que conviene conocer: lo que importa es la fracción del viewport recorrida, no los píxeles. Ochocientos píxeles son media pantalla en un portátil y una cuarta parte en un monitor grande, y la percepción sigue a la fracción. Si tu producto se usa en pantallas muy distintas, expresa el recorrido en unidades de viewport y aplica la tabla sobre eso.

El tamaño también cuenta

A igualdad de recorrido, un elemento grande necesita más tiempo que uno pequeño. Hay dos razones y las dos son mecánicas.

Un elemento grande barre más campo visual. Un panel de seiscientos por ochocientos píxeles moviéndose doscientos píxeles cambia muchos más píxeles que un chip de ochenta por treinta haciendo el mismo recorrido, y el sistema visual procesa esa cantidad de cambio como más movimiento del que indicaría la distancia.

Un elemento grande sugiere más masa. El movimiento en interfaz hereda las expectativas del mundo físico, y en el mundo físico los objetos grandes aceleran y frenan más despacio. Un panel grande que se mueve exactamente igual de rápido que un chip se percibe como si no pesara nada, y eso rompe la ilusión de solidez que hace que una interfaz se sienta bien construida.

La corrección práctica, sin inventar una segunda fórmula: sube un escalón de la escala cuando el elemento ocupe más de un tercio del viewport, y baja uno cuando ocupe menos de una décima parte. Con eso, un tooltip de cien píxeles y un panel a pantalla completa que recorren la misma distancia acaban con duraciones distintas y las dos se sienten correctas.

Y el caso del escalado, que se olvida: cuando lo que cambia es el tamaño y no la posición, la magnitud relevante no es la distancia sino la razón entre el tamaño final y el inicial. Una tarjeta que pasa de 0,96 a 1 es un cambio minúsculo y va con --mov-corta; una que pasa de 0,3 a 1 recorre mucho más en términos perceptivos y necesita --mov-larga.

Medirlo en tiempo de ejecución

Cuando la distancia no se conoce de antemano —una tarjeta que se expande desde donde estuviera, un elemento que se reordena— el cálculo se hace midiendo. Este ayudante devuelve la duración adecuada y la redondea al escalón de la escala más cercano, para que el sistema siga cerrado.

const ESCALA = [100, 150, 250, 320, 400, 600];

function ajustarAEscala(ms) {
  return ESCALA.reduce((mejor, v) => (Math.abs(v - ms) < Math.abs(mejor - ms) ? v : mejor));
}

/**
 * Duracion perceptivamente coherente para un recorrido dado.
 * base y distanciaBase definen el punto de anclaje de la curva.
 */
export function duracionPorDistancia(distancia, { base = 250, distanciaBase = 200 } = {}) {
  const d = Math.max(Math.abs(distancia), 1);
  const cruda = base * Math.sqrt(d / distanciaBase);
  return ajustarAEscala(Math.min(cruda, 500));
}

/** Recorrido real entre dos cajas, en pixeles. */
export function recorrido(cajaA, cajaB) {
  const dx = cajaB.left - cajaA.left;
  const dy = cajaB.top - cajaA.top;
  return Math.hypot(dx, dy);
}

Y el uso dentro de una transición de tipo FLIP, donde la distancia solo se conoce después de mutar el DOM:

export function moverConMedida(elemento, mutar) {
  const antes = elemento.getBoundingClientRect();
  mutar();                                   // el DOM cambia, el elemento salta
  const despues = elemento.getBoundingClientRect();

  const dx = antes.left - despues.left;
  const dy = antes.top - despues.top;
  if (dx === 0 && dy === 0) return null;

  const duracion = duracionPorDistancia(Math.hypot(dx, dy));
  const fraccion = (despues.width * despues.height) / (innerWidth * innerHeight);
  const ajustada = fraccion > 0.33 ? subirEscalon(duracion)
                 : fraccion < 0.1  ? bajarEscalon(duracion)
                 : duracion;

  return elemento.animate(
    [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "none" }],
    { duration: ajustada, easing: "cubic-bezier(0.4, 0, 0.2, 1)" }
  );
}

const subirEscalon = (v) => ESCALA[Math.min(ESCALA.indexOf(v) + 1, ESCALA.length - 1)];
const bajarEscalon = (v) => ESCALA[Math.max(ESCALA.indexOf(v) - 1, 0)];

La curva es la estándar porque el elemento ya estaba en pantalla y solo se mueve dentro de ella. Y el Math.min a quinientos milisegundos impone el techo duro: por larga que sea la distancia, nada que esté en el camino del usuario supera el medio segundo.

💡
Dónde compensa esto y dónde no

Calcular la duración en tiempo de ejecución solo merece la pena cuando la distancia varía mucho entre ejecuciones: listas que se reordenan, elementos que se expanden desde una posición cualquiera, transiciones compartidas. Para un modal que siempre entra desde el mismo sitio, el número de la tabla es suficiente y más fácil de mantener.

La coherencia de un producto no se percibe en los valores: se percibe en las relaciones entre ellos

Hay algo contraintuitivo en cómo funciona la percepción de coherencia de un sistema de movimiento, y entenderlo cambia dónde inviertes el esfuerzo. Nadie percibe una duración absoluta. Nadie ha mirado nunca una transición y ha pensado “eso son doscientos cincuenta milisegundos”; lo que se percibe siempre es una relación: esto es más rápido que aquello, esto pesa más que aquello, esto pertenece al mismo grupo que aquello. Y las relaciones se rompen de una forma muy concreta que no tiene nada que ver con el valor de cada animación por separado: se rompen cuando dos elementos que hacen la misma clase de cosa tienen duraciones distintas, o cuando dos que hacen cosas distintas tienen la misma. Un producto donde absolutamente todo dura doscientos cincuenta milisegundos se siente plano y monótono, aunque cada animación tomada por separado sea correcta, porque la interfaz ha dejado de distinguir entre un chip y un panel a pantalla completa y esa distinción existía en el mundo y ahora ha desaparecido. Un producto donde las duraciones se derivan de la distancia, del tamaño y de la frecuencia se siente construido, porque los objetos grandes se comportan como grandes y los pequeños como pequeños, exactamente igual que fuera de la pantalla. Y aquí está la parte que resulta liberadora para quien se agobia con los sistemas de diseño: el valor absoluto importa mucho menos de lo que la gente cree. Un sistema con base de 220 milisegundos y otro con base de 280 producen productos que se sienten igual de coherentes, siempre que las relaciones internas se mantengan; lo que arruina la sensación no es haber elegido mal el ancla, es que la tabla no se aplique de forma consistente. Esa es la razón por la que un sistema de movimiento imperfecto y aplicado en todas partes gana siempre a uno perfecto aplicado en tres pantallas, y por la que el esfuerzo debe ir a la cobertura antes que a la precisión.