wandres.dev
EASING · cubic-bezier, steps y linear()

Leer una curva de Bézier cúbica antes de ejecutarla

Qué significan los cuatro números de cubic-bezier, cómo se traducen en velocidad, y cómo predecir el movimiento sin abrir un editor de curvas.

⏱ 18 min

cubic-bezier(0.34, 1.56, 0.64, 1) no es una cadena mágica que copias de un generador: son las coordenadas de dos puntos, y con un poco de práctica se leen igual que se lee una hora en un reloj. Saber leerlas cambia la forma de trabajar, porque deja de hacer falta probar veinte curvas para encontrar la que se siente bien: sabes qué buscas y escribes los números. Esta lección enseña esa lectura y el porqué de cada regla.

🎯 Al terminar esta lección sabrás
  • Interpretar los cuatro números de cubic-bezier como dos puntos de control.
  • Deducir la velocidad inicial y final de una curva a partir de sus coordenadas.
  • Explicar por qué el eje horizontal está acotado y el vertical no.
  • Reconocer las cinco curvas con nombre por sus coordenadas exactas.

Los cuatro números son dos puntos

Una curva de Bézier cúbica se define con cuatro puntos. En una función de suavizado, dos de ellos son fijos: el punto inicial está en el origen, y el final en la esquina opuesta. Los cuatro números que escribes son las coordenadas de los dos puntos de control intermedios.

El eje horizontal es la fracción de tiempo transcurrido, de 0 a 1. El eje vertical es la fracción de progreso de la animación, también de 0 a 1 en el caso normal. La curva es, literalmente, la respuesta a la pregunta “cuando haya pasado esta fracción del tiempo, ¿qué fracción del recorrido llevo hecha?”.

Los puntos de control no están sobre la curva: la atraen. La curva sale del origen en la dirección del primer punto de control y llega al final viniendo de la dirección del segundo. Esa es toda la intuición geométrica que hace falta, y de ella salen las dos lecturas que importan.

La velocidad inicial es la pendiente hacia el primer punto de control, es decir y1 dividido entre x1. Un primer punto de (0.05, 0.7) da una pendiente de 14: arranque explosivo. Uno de (0.6, 0) da pendiente 0: arranque completamente parado, que se lee como masa.

La velocidad final es la pendiente desde el segundo punto de control hasta la esquina, es decir (1 - y2) entre (1 - x2). Un segundo punto de (0, 1) da pendiente 0: aterrizaje suave. Uno de (1, 1) deja la pendiente indeterminada hacia arriba: el elemento llega a toda velocidad y se para en seco.

Con esas dos lecturas puedes describir cualquier curva sin dibujarla. cubic-bezier(0.4, 0, 0.2, 1): arranque con pendiente 0 —masa— y aterrizaje con pendiente 0 —llegada suave—. Es una curva de traslado, con inercia a los dos lados.

Cómo evalúa el motor la curva

El detalle técnico que hace falta para entender la restricción del eje horizontal: una Bézier no es una función de x, es una curva paramétrica. Se define en función de un parámetro interno t que va de 0 a 1, y produce a la vez una coordenada horizontal y una vertical:

x(t) = 3(1-t)²·t·x1 + 3(1-t)·t²·x2 + t³
y(t) = 3(1-t)²·t·y1 + 3(1-t)·t²·y2 + t³

Cuando el motor tiene una fracción de tiempo p y quiere saber el progreso, no puede evaluar directamente: tiene que resolver x(t) = p para encontrar t, y luego evaluar y(t). Esa resolución se hace numéricamente, típicamente con Newton-Raphson y una bisección de respaldo.

De ahí sale la restricción: x1 y x2 tienen que estar entre 0 y 1. Si estuvieran fuera, la curva podría retroceder en el eje del tiempo y habría fracciones de tiempo con dos progresos posibles, lo que no tiene sentido: el tiempo no vuelve atrás. Un valor de x fuera de rango hace la declaración inválida y el motor la descarta entera, cayendo a ease.

y1 e y2 no están acotados, y esa asimetría es deliberada: el progreso sí puede ir hacia atrás o pasarse del destino. Un y1 negativo produce anticipación —el elemento retrocede antes de avanzar—. Un y2 mayor que 1 produce sobrepaso —el elemento se pasa y vuelve—.

/* Anticipacion: retrocede antes de salir. y1 negativo. */
.anticipa { transition: translate 400ms cubic-bezier(0.68, -0.6, 0.32, 1); }

/* Sobrepaso: se pasa del destino y vuelve. y2 mayor que 1. */
.sobrepasa { transition: translate 400ms cubic-bezier(0.34, 1.56, 0.64, 1); }

Merece la pena calcular cuánto se pasa una curva de sobrepaso, porque el número decide si se lee como inercia o como espectáculo. Para cubic-bezier(0.34, 1.56, 0.64, 1) el máximo ronda el 10 por ciento del recorrido: sobre un desplazamiento de 200 píxeles son 20 píxeles de más, que es mucho. Para el 1.16 que recomendaba la lección de continuación, el sobrepaso ronda el 2 por ciento: 4 píxeles, que es inercia.

Una función de un párrafo permite medirlo sin adivinar:

// Progreso maximo de una curva: cuanto se pasa del destino.
function sobrepaso(x1, y1, x2, y2, muestras = 1000) {
  const y = (t) => 3 * (1 - t) ** 2 * t * y1 + 3 * (1 - t) * t ** 2 * y2 + t ** 3;
  let max = 0;
  for (let i = 0; i <= muestras; i++) max = Math.max(max, y(i / muestras));
  return max;
}

console.log(sobrepaso(0.34, 1.56, 0.64, 1).toFixed(3));  // ~1.101
console.log(sobrepaso(0.34, 1.16, 0.64, 1).toFixed(3));  // ~1.021
Las curvas simétricas son casi siempre la elección equivocada, y la razón es física

Hay un sesgo estético muy extendido hacia las curvas simétricas —ease-in-out y sus variantes— porque en un gráfico se ven equilibradas y elegantes. En movimiento son casi siempre peores que una asimétrica, y el motivo no es de gusto sino de qué representa cada mitad. Arrancar y frenar no son la misma operación. Arrancar es vencer la inercia de un objeto en reposo, y en el mundo físico eso es un proceso relativamente lento cuya duración depende de la masa. Frenar, en cambio, casi nunca es simétrico: los objetos se paran por rozamiento, por choque o por una fuerza aplicada, y todos esos procesos tienen perfiles distintos y en general más abruptos que el arranque. Una curva simétrica describe un objeto que decelera con la misma suavidad exquisita con la que aceleró, y eso solo ocurre en el mundo real cuando algo está siendo movido por un mecanismo controlado: un ascensor, un brazo robótico, una puerta con amortiguador. Por eso ease-in-out se lee siempre un poco mecánica, y por eso funciona bien precisamente en los contextos donde quieres transmitir que hay una máquina detrás. Para todo lo demás la asimetría es lo correcto, y la asimetría útil tiene una dirección concreta: más tiempo en la salida que en la entrada. La razón es que la parte de la animación que informa al usuario es el final —dónde acabó la cosa— y estirar el final le da más muestras del destino. Estirar el principio le da más muestras del origen, que ya conocía. Si tuvieras que quedarte con una sola curva para toda una interfaz, la elección correcta no es ease ni ease-in-out: es algo del estilo de cubic-bezier(0.2, 0, 0, 1), con arranque perezoso y cola larga.

Las cinco con nombre

Las palabras clave son atajos con valores exactos definidos en la especificación. Merece la pena conocerlos porque explican por qué se comportan como lo hacen.

Palabra clave Equivalente Lectura
linear cubic-bezier(0, 0, 1, 1) Velocidad constante, sin masa
ease cubic-bezier(0.25, 0.1, 0.25, 1) Arranque rápido, cola larga
ease-in cubic-bezier(0.42, 0, 1, 1) Arranca parado, llega a tope
ease-out cubic-bezier(0, 0, 0.58, 1) Arranca a tope, aterriza suave
ease-in-out cubic-bezier(0.42, 0, 0.58, 1) Simétrica

Dos cosas de esa tabla sorprenden.

La primera: ease no es simétrica ni neutra. Su primer punto de control en (0.25, 0.1) da una pendiente inicial de 0.4, moderada, y su segundo en (0.25, 1) deja una cola muy larga. Es una curva razonable como valor por defecto, y es más lenta al final de lo que la gente cree.

La segunda: ease-in casi nunca es lo que quieres. Llega al destino a máxima velocidad y se para en seco, lo que se lee como un choque. Su uso legítimo es la salida de elementos que desaparecen, donde el frenazo no se ve porque el elemento ya no está.

Y una advertencia de nomenclatura que confunde a mucha gente: ease-in significa “entra en el movimiento poco a poco”, no “es la curva de entrada del elemento”. Para animar la entrada de un elemento quieres ease-out. Los nombres describen la aceleración, no el papel en la interfaz.

⚔️ Lee cinco curvas sin dibujarlas
  1. Para cada una de estas cinco, calcula a mano la pendiente inicial y la final: cubic-bezier(0.4, 0, 0.2, 1), cubic-bezier(0, 0, 0.2, 1), cubic-bezier(0.8, 0, 1, 1), cubic-bezier(0.25, 1, 0.5, 1), cubic-bezier(0.5, 0, 0.5, 1).
  2. Escribe en una línea qué se lee de cada una antes de probarlas.
  3. Pruébalas sobre un desplazamiento de 300 píxeles y corrige lo que hayas fallado.
  4. Con la función de sobrepaso, mide cuánto se pasan tres curvas de rebote de las que circulan por ahí y decide cuáles son inercia y cuáles espectáculo.