Los eases configurables: back, elastic, bounce y steps
Las cuatro familias que aceptan parámetros, qué significa cada número, y las cifras exactas de sobrepaso que produce cada configuración.
Cuatro familias del catálogo aceptan configuración, y son precisamente las que se salen del rango de progreso normal o lo rompen en trozos. back se pasa del destino una vez, elastic oscila alrededor de él, bounce rebota contra él, y steps renuncia a la continuidad. Las tres primeras son intentos de simular física con una fórmula cerrada, y saber exactamente qué hace cada parámetro —con números, no con adjetivos— es lo que separa ajustarlas de darles al azar hasta que quede bien.
- Configurar el sobrepaso de
backy predecir hasta dónde se pasa. - Ajustar amplitud y periodo de
elasticsabiendo qué controla cada uno. - Distinguir
bouncedeelasticpor lo que cada uno simula. - Usar
stepscon su segundo parámetro y saber cuándo importa.
back: un solo sobrepaso
back toma un parámetro, el sobrepaso, cuyo valor por defecto es 1.70158. Ese número tan raro viene de las ecuaciones de Robert Penner y está elegido para producir un sobrepaso de exactamente el 10%.
gsap.to('.caja', { x: 300, duration: 0.6, ease: 'back.out(1.7)' });
Cuánto se pasa el elemento según el valor:
| Configuración | Máximo alcanzado |
|---|---|
back.out (por defecto) |
1,1000 |
back.out(2.5) |
1,1890 |
back.out(4) |
1,3793 |
Con el valor por defecto, un panel que recorre 400 píxeles se pasa 40 y vuelve. Con back.out(4), se pasa 152, que ya es una exageración deliberada y en una interfaz seria se lee como un error.
Las tres variantes hacen cosas distintas y conviene tenerlas claras. back.out se pasa al final, que es el rebote clásico de llegada. back.in se pasa al principio, en sentido contrario: el elemento retrocede antes de salir, que es la anticipación de la animación tradicional. back.inOut hace las dos.
// Anticipacion: retrocede un poco antes de salir disparado.
gsap.to('.flecha', { x: 200, duration: 0.5, ease: 'back.in(2)' });
back.in es de los eases peor aprovechados del catálogo. La anticipación —el movimiento contrario que precede a la acción— es uno de los principios fundamentales de la animación clásica y en interfaces casi no se usa, cuando es exactamente lo que hace que un botón que dispara algo se sienta con intención en vez de con desgana.
elastic: dos parámetros y una oscilación
elastic toma amplitud y periodo, con valores por defecto de 1 y 0.3 para las variantes in y out, y 1 y 0.45 para inOut.
gsap.to('.caja', { x: 300, duration: 1.1, ease: 'elastic.out(1, 0.3)' });
La amplitud escala el tamaño del sobrepaso. Con 1, el máximo es 1,3731: se pasa un 37%. Con 2, el máximo sube a 2,0553, es decir, el elemento llega a estar al doble de la distancia que tenía que recorrer. Valores por debajo de uno no reducen el sobrepaso de la forma que esperarías, porque el motor ajusta el periodo internamente para que la curva siga arrancando en cero; para amortiguar el efecto se usa el periodo, no la amplitud.
El periodo controla cada cuánto oscila, y su efecto sobre el sobrepaso es indirecto y muy fuerte: como el primer pico ocurre más tarde con periodos grandes, la envolvente exponencial ha decaído más cuando llega, y el sobrepaso resulta menor.
| Periodo | Máximo alcanzado |
|---|---|
0.2 |
1,5122 |
0.3 (por defecto) |
1,3731 |
0.6 |
1,1535 |
Con elastic.out(1, 0.3), la trayectoria muestreada es esta:
| Progreso | 0,1 | 0,2 | 0,3 | 0,5 | 0,8 |
|---|---|---|---|---|---|
| Valor | 1,250 | 1,125 | 0,875 | 1,016 | 1,002 |
Se lee la física: se pasa un 25%, vuelve, se queda corto un 12,5%, y se va apagando. Es una oscilación amortiguada, igual que la de un muelle subamortiguado, con la diferencia de que aquí la envolvente y la frecuencia son parámetros libres en vez de consecuencias de una masa y una rigidez.
Un elastic con duración corta no se ve como elástico: se ve como un temblor. La oscilación necesita tiempo para leerse como oscilación, y por debajo de unos 600 milisegundos con el periodo por defecto el ojo solo registra un parpadeo. Si la duración de tu animación está fijada por el diseño y es corta, back es la elección correcta y elastic no lo es, por mucho que la curva sea más interesante.
bounce: rebotes contra un suelo
bounce no toma parámetros. Es una función a trozos con cuatro parábolas de amplitud decreciente, y simula algo conceptualmente distinto de elastic: una pelota que cae y rebota contra una superficie.
La diferencia importa. elastic oscila alrededor del destino, pasándose por los dos lados. bounce nunca se pasa: llega al destino, rebota hacia atrás, vuelve, rebota menos, y así hasta pararse.
| Progreso | 0,4 | 0,75 | 0,92 |
|---|---|---|---|
bounce.out |
0,910 | 0,973 | 0,993 |
Ninguno de esos valores supera el uno, y ninguno lo hará. Eso lo convierte en el único ease de esta familia que se puede usar sobre propiedades acotadas sin romper nada: una opacidad con elastic produce valores por encima de uno y por debajo de cero, que el navegador recorta y por tanto se ven como pausas planas; con bounce no ocurre.
bounce.out es el que simula la caída. bounce.in es su inverso y se lee como algo que va cogiendo carrerilla a saltos, que es un efecto extraño y rara vez útil.
steps: renunciar a la continuidad
steps toma un número de pasos y, opcionalmente, un segundo argumento booleano que hace que el primer salto ocurra inmediatamente en lugar de al final del primer intervalo.
gsap.to('.sprite', {
backgroundPosition: '-1200px 0',
duration: 1,
ease: 'steps(12)',
repeat: -1,
});
Con steps(4), el valor a lo largo del recorrido es este:
| Progreso | 0 | 0,1 | 0,3 | 0,5 | 0,7 | 0,9 | 1 |
|---|---|---|---|---|---|---|---|
| Valor | 0,00 | 0,00 | 0,25 | 0,50 | 0,75 | 1,00 | 1,00 |
Cuatro pasos producen cinco valores distintos —incluidos los dos extremos— y el elemento pasa el mismo tiempo en cada uno. Es la curva de las animaciones de sprites, de los contadores que saltan de unidad en unidad, y de cualquier cosa que deba sentirse mecánica.
El segundo parámetro se nota en los extremos: con steps(4, true) el valor arranca ya en 0.25 en lugar de en 0, lo cual importa cuando cada paso corresponde a un fotograma de una hoja de sprites y el primero ya está pintado en el estado inicial.
steps también es la forma correcta de conseguir un efecto de “baja frecuencia de imagen” deliberado, muy usado para dar aspecto de animación tradicional a doce fotogramas por segundo: un steps(12) sobre un tween de un segundo produce exactamente eso.
Vale la pena decirlo con claridad porque explica cuándo fallan. back es un muelle al que le han dado un único rebote; elastic es un muelle subamortiguado con la envolvente y la frecuencia elegidas a mano. Ambos imitan la forma de una oscilación amortiguada, y ninguno tiene su dinámica: la duración sigue siendo un parámetro fijo, el sobrepaso no depende de la distancia recorrida, y no hay ninguna noción de velocidad al interrumpir.
De ahí salen los dos fallos característicos. El primero: la misma configuración de elastic aplicada a un desplazamiento de 500 píxeles y a uno de 15 produce un sobrepaso proporcional en ambos casos, es decir, 185 píxeles en uno y 5,5 en el otro. Físicamente eso es correcto para un muelle real, pero como la duración no cambia, el pequeño oscila a la misma frecuencia que el grande, y un objeto pequeño vibrando lento se percibe como un fallo de renderizado.
El segundo: interrumpir un elastic a mitad de la oscilación produce un salto de velocidad que ningún objeto físico haría, y el ojo lo detecta precisamente porque el resto de la curva le había prometido física.
La regla que se deduce: usa back y elastic en animaciones disparadas, cortas y no interrumpibles —una confirmación, un icono que aparece, un botón que responde—. Para gestos continuos o cualquier cosa que el usuario pueda cortar a mitad, la respuesta es un muelle de verdad, no una curva que se le parece.
Mide el sobrepaso empíricamente: crea un tween sobre un objeto con una sola propiedad numérica que vaya de 0 a 1, registra el valor máximo en el onUpdate, y compáralo con las tablas de esta lección para back.out, back.out(4), elastic.out(1, 0.3) y elastic.out(2, 0.3). Después haz lo mismo con bounce.out y comprueba que nunca supera el uno.