wandres.dev
FÍSICA DEL MOVIMIENTO · Muelles, inercia y amortiguación

Por qué un muelle se siente real y una Bézier no

La diferencia entre describir una trayectoria y simular una causa: qué información contiene un sistema dinámico que una curva de easing no puede contener, y por qué eso se nota.

⏱ 18 min

Hay una diferencia que se percibe antes de poder nombrarla: dos animaciones de la misma duración y la misma distancia, una hecha con una Bézier bien elegida y otra con un muelle, no se sienten igual. La explicación habitual —“el muelle rebota”— es falsa, porque un muelle sobreamortiguado no rebota nada y sigue sintiéndose distinto. La explicación real es que son dos categorías de objeto matemático que responden a preguntas diferentes, y solo una de las dos tiene algo que decir cuando el usuario interrumpe.

🎯 Al terminar esta lección sabrás
  • Distinguir una descripción cinemática de una simulación dinámica y ubicar cada API en su categoría.
  • Explicar por qué la duración de un muelle es una consecuencia y no un parámetro.
  • Identificar el momento exacto en el que una curva de easing produce una discontinuidad de velocidad.
  • Escribir el estado mínimo que necesita un sistema dinámico y ver por qué una Bézier no lo tiene.

Cinemática contra dinámica

Una curva de easing es una función. Le das una fracción del tiempo transcurrido, entre cero y uno, y te devuelve una fracción del recorrido, también entre cero y uno. La animación completa es esa función más un valor inicial, un valor final y una duración. Todo el sistema cabe en cuatro datos y una evaluación sin memoria: para saber dónde está el objeto en el instante t no hace falta saber dónde estaba en t - 1. Es una descripción cinemática: describe la trayectoria y calla sobre las causas.

Un muelle es otra cosa. No hay ninguna función que te dé la posición a partir del tiempo; lo que hay es una regla sobre las fuerzas. La regla dice que un muelle empuja hacia su punto de reposo con una fuerza proporcional a lo estirado que está, y que un amortiguador se opone al movimiento con una fuerza proporcional a la velocidad. De esas dos frases y de la segunda ley de Newton sale una ecuación diferencial, y de resolverla —analítica o numéricamente— sale la trayectoria. Es una descripción dinámica: describe las causas y deja que la trayectoria sea la consecuencia.

Esa diferencia no es filosófica. Tiene tres consecuencias medibles.

La primera es que el sistema dinámico tiene estado. En cada instante conoce dos números, posición y velocidad, y de ellos deriva el siguiente instante. La Bézier solo conoce la fracción de tiempo. Ese estado extra es información que existe en el mundo real y que la Bézier tira a la basura.

La segunda es que la duración es emergente. En una Bézier la duración es un parámetro que tú fijas: la curva vale de cero a uno pase lo que pase, y el motor la estira al tiempo que hayas dicho. Un muelle no sabe cuánto va a tardar. Tarda lo que tarde según su rigidez, su masa, su amortiguación y —esto es lo importante— la distancia y la velocidad de partida. Un muelle que tiene que recorrer diez píxeles llega antes que el mismo muelle recorriendo trescientos, exactamente igual que un objeto físico. Con una Bézier ambos tardan lo mismo, que es exactamente lo que no pasa en el mundo.

La tercera es la interrumpibilidad, y es la que se nota.

El momento en que se rompe la ilusión

Imagina un panel que se despliega con transition: transform 300ms cubic-bezier(.2,.8,.2,1). El usuario lo abre, y a mitad del recorrido cambia de opinión y lo cierra. El navegador hace lo único que puede hacer: crea una transición nueva desde la posición actual hasta la posición cerrada, con la misma curva y la misma duración.

El problema es la velocidad. En el instante de la interrupción el panel se movía hacia arriba a cierta velocidad. La nueva transición arranca desde el reposo, porque la curva cubic-bezier(.2,.8,.2,1) empieza con pendiente definida por su primer punto de control y no tiene forma de saber a qué velocidad venía el objeto. El resultado es una discontinuidad de velocidad: el panel iba hacia arriba a doscientos píxeles por segundo y en un frame pasa a ir hacia abajo a cero. Ningún objeto con masa hace eso. El ojo lo detecta como un tirón, y aunque el usuario no sepa nombrarlo, la animación deja de sentirse física.

Un muelle no tiene ese problema porque el cambio de objetivo no reinicia nada: solo cambia el punto hacia el que apunta la fuerza de recuperación. La velocidad en el momento del cambio sigue siendo parte del estado. El panel decelera, invierte y vuelve, con la trayectoria continua que tendría un objeto real al que empujas en sentido contrario mientras se mueve.

// Cinematica: el estado es el tiempo, y punto.
function bezierEnT(t, duracion, desde, hasta, curva) {
  const p = Math.min(1, t / duracion);
  return desde + (hasta - desde) * curva(p);
}

// Dinamica: el estado es la posicion Y la velocidad.
// El objetivo puede cambiar en cualquier momento sin reiniciar nada.
function pasoMuelle(estado, objetivo, k, c, m, dt) {
  const fuerzaMuelle = -k * (estado.x - objetivo);
  const fuerzaAmort  = -c * estado.v;
  const a = (fuerzaMuelle + fuerzaAmort) / m;
  const v = estado.v + a * dt;
  const x = estado.x + v * dt;
  return { x, v };
}

Fíjate en que pasoMuelle no recibe ni duración ni progreso. No los necesita y no podría usarlos. Y fíjate en que objetivo es un argumento del paso, no del sistema: cambiarlo a mitad de camino es una operación legal y barata.

ℹ️
Las transiciones nativas sí interpolan, pero no la velocidad

Los navegadores modernos hacen algo mejor que reiniciar en seco: al interrumpir una transición toman como valor de partida el valor computado en ese instante, no el valor final. Eso resuelve el salto de posición, que sería mucho más visible. Lo que no resuelven, ni pueden, es la continuidad de la derivada, porque la información de velocidad no forma parte del modelo de una transición CSS.

Qué se siente exactamente

Vale la pena ser preciso sobre qué percibe el ojo, porque “se siente natural” no es un criterio con el que se pueda diseñar.

Lo que percibimos como peso es la aceleración inicial suave. Un objeto con masa no alcanza su velocidad de crucero instantáneamente; la fuerza tiene que actuar durante un rato. Un muelle produce eso por construcción, porque en el instante cero la velocidad es cero y solo hay fuerza. Una Bézier lo produce si eliges bien los puntos de control, y no lo produce si eliges mal: cubic-bezier(0, .8, .2, 1) arranca con pendiente vertical, y eso se lee como un objeto sin masa que teletransporta.

Lo que percibimos como energía disipándose es que las oscilaciones sucesivas decrecen en una proporción constante. El oído tiene la misma sensibilidad con el sonido de un objeto que rebota. Un muelle subamortiguado da exactamente esa progresión geométrica porque el decaimiento es exponencial. Una Bézier con rebote lo simula con un solo rebote, y por eso los back.out se leen como “un rebote”, nunca como “un objeto que se asienta”.

Y lo que percibimos como respuesta es la continuidad bajo interrupción. Es lo que hace que arrastrar y soltar con inercia se sienta correcto y que un menú que se abre y se cierra rápido se sienta pegajoso. No es un adorno: es el 90% de la razón por la que las interfaces táctiles bien hechas se sienten bien.

La pregunta correcta no es cuál es mejor sino qué tipo de animación es

Después de todo esto suena a que los muelles ganan siempre, y es falso. Una animación no interrumpible y coreografiada —una secuencia de entrada, una transición entre pantallas, un onboarding— quiere una duración exacta porque tiene que encajar con las demás piezas de la secuencia, y ahí un sistema cuya duración es emergente es un estorbo: no puedes decir “esto empieza cuando aquello lleva el 60%” si no sabes cuánto dura aquello. Ahí la Bézier es la herramienta correcta y el muelle es un capricho caro. El criterio real es este: si la animación responde a un gesto continuo del usuario o puede ser interrumpida a mitad, usa un sistema dinámico; si es una coreografía con tiempos que tienen que cuadrar entre sí, usa curvas. Los sistemas de diseño que aciertan tienen las dos escalas y dicen cuándo va cada una; los que fallan eligen una y la aplican a todo.

⚔️ Reto práctico

Reproduce la discontinuidad a mano para verla: un elemento con transition: transform 400ms ease-out que alterne entre dos posiciones al hacer clic, y un contador que registre en onUpdate la posición cada frame. Calcula la velocidad como diferencia entre frames consecutivos y píntala en un <canvas>. Interrumpe la animación a mitad y observa el escalón vertical en la gráfica de velocidad. Ese escalón es lo que el ojo detecta.