El EasePack y CustomEase
Los tres eases que no están en el núcleo —rough, slow y expoScale— con su configuración exacta, y cómo definir curvas arbitrarias con CustomEase a partir de un trazado o de un cubic-bezier.
Tres eases viven fuera del núcleo, en un fichero aparte llamado EasePack, y no por capricho de empaquetado: son curvas caras de generar que la mayoría de los proyectos no usa. Los tres resuelven problemas muy concretos —vibración, cámara lenta con entrada y salida suaves, y escalado perceptualmente uniforme— que ninguna curva del catálogo cubre. Y en un paquete todavía más aparte está CustomEase, que es la salida definitiva: cualquier curva imaginable definida por sus puntos de control.
- Cargar el EasePack y saber qué pasa si falta.
- Configurar
roughcon sus seis propiedades yslowcon sus tres parámetros. - Explicar qué corrige
expoScaley por qué el escalado lineal no se percibe lineal. - Definir curvas propias con
CustomEasedesde un trazado SVG o desde uncubic-bezier.
Cargar lo que no está en el núcleo
Los tres eases de este grupo llegan juntos:
import { gsap } from 'gsap';
import { EasePack } from 'gsap/EasePack';
import { CustomEase } from 'gsap/CustomEase';
gsap.registerPlugin(EasePack, CustomEase);
Sin ese registro, escribir ease: 'rough' no produce ningún error: GSAP no encuentra ese nombre en su mapa y aplica el ease por defecto, power1.out. La animación corre y se siente distinta de como esperabas, sin ninguna pista en la consola. Es el mismo fallo silencioso del que hablamos con los plugins, y en los eases es todavía más difícil de detectar porque el resultado sigue siendo una animación plausible.
Los tres eases del EasePack
rough: vibración controlada
rough desvía la curva de un ease de referencia mediante puntos colocados al azar, produciendo un movimiento entrecortado. Se configura con un objeto dentro de la cadena.
| Propiedad | Qué hace | Por defecto |
|---|---|---|
template |
Ease que sirve de guía general | "none" |
strength |
Cuánto se alejan los puntos de la guía | 1 |
points |
Número de puntos, es decir, frecuencia de las sacudidas | 20 |
taper |
Atenúa la rugosidad hacia "in", "out", "both" o "none" |
"none" |
randomize |
Si es false, los puntos zigzaguean de forma regular |
true |
clamp |
Si es true, impide salirse del rango entre inicio y fin |
false |
// Valores por defecto.
gsap.from(elemento, { duration: 1, opacity: 0, ease: 'rough' });
// Configurado.
gsap.to(elemento, {
duration: 2,
y: 300,
ease: 'rough({strength: 3, points: 50, template: strong.inOut, taper: both, randomize: false})',
});
Fíjate en la sintaxis: es un objeto dentro de la cadena, sin comillas alrededor de los valores de texto. Es una peculiaridad del analizador de eases de GSAP y la fuente de erratas: template: 'strong.inOut' con comillas dentro de la cadena no funciona.
clamp es la propiedad que decide si rough es utilizable sobre una propiedad acotada. Sin él, animar una opacidad de 0 a 1 con rough produce valores fuera del rango que el navegador recorta, y el resultado son mesetas planas donde debería haber vibración. Con clamp: true, todos los puntos se quedan dentro.
taper: "out" es la configuración más útil del conjunto: la vibración es fuerte al principio y se calma hacia el final, que es lo que hace un objeto que se estabiliza. Combinado con randomize: false produce un zigzag regular decreciente, que se lee como una vibración mecánica en vez de como ruido.
slow: cámara lenta con entrada y salida
slow produce un movimiento que decelera al principio, avanza linealmente durante una porción central, y vuelve a acelerar al final. Su caso de uso canónico es texto que entra en pantalla, se mueve despacio el tiempo suficiente para leerse, y sale.
Toma tres parámetros: linearRatio, power y yoyoMode.
linearRatio es la fracción del recorrido que es lineal, entre cero y uno, y su valor por defecto es 0.7. Con 0.5, el primer 25% decelera, el 50% central es lineal y el último 25% acelera. Con 0.8, quedan solo el 10% en cada extremo para las curvas.
power es la fuerza de las curvas de los extremos, con valor por defecto 0.7. Valores mayores que uno invierten el tramo lineal central, lo cual produce efectos raros que a veces son útiles.
yoyoMode, un booleano, es la parte ingeniosa. Sirve para crear animaciones acompañantes que se sincronizan solas con la principal. El caso: quieres que el texto que entra y sale con slow además aparezca y desaparezca con opacidad, apareciendo justo antes de que empiece el tramo lineal y desapareciendo justo cuando termina. Calcular esas dos duraciones a mano es tedioso y se rompe al tocar el linearRatio. Con yoyoMode es un tween más con la misma duración y el mismo ease.
// El movimiento principal.
gsap.to(texto, { duration: 5, x: 600, ease: 'slow(0.5, 0.8)' });
// La opacidad acompanante: misma duracion, mismo ease, yoyoMode a true.
gsap.from(texto, { duration: 5, opacity: 0, ease: 'slow(0.5, 0.8, true)' });
Sin slow, la forma de conseguir este efecto era encadenar tres tweens —uno con out, uno sin ease y uno con in— y el problema era que las curvas no empalmaban con la misma derivada, así que se veían saltos de velocidad en las uniones. slow es una sola curva continua.
expoScale: el escalado que no se percibe lineal
Este es el más interesante conceptualmente. Hay un fenómeno perceptivo que ocurre al animar la escala de un objeto: aunque uses un ease lineal, el movimiento no se percibe uniforme.
La razón es que la percepción del cambio de tamaño es relativa, no absoluta. Pasar de escala 1 a escala 2 es duplicar; pasar de 2 a 3 es aumentar un 50%. Un ease lineal reparte el mismo incremento absoluto en cada frame, así que al principio los incrementos son proporcionalmente enormes y al final proporcionalmente pequeños. El resultado se ve como un zoom que arranca disparado y se arrastra al final.
expoScale dobla la curva para compensar, de modo que el cambio proporcional sea constante. Necesita saber las escalas de partida y de llegada, que se pasan en la cadena:
// De escala 1 a escala 2.
gsap.to('#imagen', { duration: 1, scale: 2, ease: 'expoScale(1, 2)' });
Acepta un tercer parámetro, el ease que quieres que doble, con "none" por defecto:
gsap.fromTo('#imagen',
{ scale: 0.5 },
{ duration: 1, scale: 3, ease: 'expoScale(0.5, 3, power2.inOut)' }
);
Dos restricciones prácticas. Los valores de escala no pueden ser cero, porque la matemática no funciona; se usa un valor pequeño como 0.01 en su lugar. Y no conviene irse a valores absurdamente pequeños como 0.00000001, porque entonces una parte enorme del tween se consume recorriendo escalas invisibles.
Si alguna vez has hecho un zoom sobre un mapa o una imagen que “no acababa nunca de llegar”, esta es la corrección que faltaba.
La percepción logarítmica no es exclusiva del tamaño: el volumen del sonido, el brillo y la separación entre elementos se perciben igual. expoScale solo resuelve el caso de la escala, pero el principio se aplica a mano en los demás: si estás animando una magnitud cuya percepción es proporcional, interpola su logaritmo en vez de su valor.
CustomEase: cualquier curva
CustomEase viene en su propio paquete y permite definir una curva arbitraria. Se crea una vez, se le da un nombre, y a partir de ahí se usa como cualquier otro ease.
CustomEase.create(
'hop',
'M0,0 C0,0 0.056,0.442 0.175,0.442 0.294,0.442 0.332,0 0.332,0 0.332,0 0.414,1 0.671,1 0.991,1 1,0 1,0'
);
gsap.to(elemento, { duration: 1, y: -100, ease: 'hop' });
La cadena es datos de trazado SVG con comandos cúbicos: M, C, S, L y Z. Normalmente se usan coordenadas normalizadas entre cero y uno, pero acepta cualquier escala y normaliza internamente.
Hay tres formas de obtener esa cadena. La primera es dibujarla en el visualizador de eases de la documentación oficial, que permite añadir y mover puntos de control y copiar el resultado. La segunda es pegar un <path> exportado de una herramienta de diseño; el visualizador coge el primer trazado que encuentre y lo convierte. La tercera, y la más útil en el día a día, es que CustomEase también entiende una cadena de cuatro números en formato cubic-bezier:
// Los mismos cuatro numeros que usarias en CSS.
CustomEase.create('salida', '0.16, 1, 0.3, 1');
gsap.to(elemento, { y: 0, duration: 0.6, ease: 'salida' });
Eso convierte a CustomEase en el puente entre el sistema de movimiento de tu CSS y el de tu JavaScript: las mismas cuatro cifras producen exactamente la misma curva en los dos sitios, y el lenguaje de movimiento del proyecto deja de tener dos definiciones que se desincronizan.
Hay un método adicional que casi nadie usa y es excelente para documentar: CustomEase.getSVGData(ease, config) devuelve la cadena de trazado que dibuja gráficamente cualquier ease, incluidos los estándar, al tamaño que le pidas. Sirve para pintar las curvas del proyecto en la propia documentación.
// Dibuja la curva "hop" dentro del <path id="grafica">, a 500 por 400.
CustomEase.getSVGData('hop', { width: 500, height: 400, path: '#grafica' });
// Tambien funciona con los eases estandar.
CustomEase.getSVGData('power2.inOut', { width: 500, height: 400, path: '#grafica2' });
Y en el mismo paquete conceptual viven CustomBounce y CustomWiggle, que generan curvas paramétricas de rebote y de vibración en lugar de exigir que dibujes los puntos. Se cargan por separado y se registran igual.
CustomEase.create mete la curva en el mismo mapa de nombres donde viven power2, expo y los demás. Llamar CustomEase.create('expo', ...) no crea un ease nuevo: sobrescribe el del catálogo, en toda la aplicación, para todo el código que ya escribía ease: 'expo' esperando lo de siempre.
Suena a error tonto y ocurre por un camino menos evidente: alguien crea una curva y la llama back porque su efecto se parece a un rebote, o la llama smooth sin saber que no colisiona pero mañana sí. La disciplina es prefijar todas las curvas propias con algo del dominio —app-entrada, app-salida, app-enfasis— y tenerlas todas en el mismo módulo, junto al registro de plugins.
Y un segundo problema del mismo origen: el registro ocurre cuando el módulo que llama a create se evalúa. Si esas definiciones están en un fichero que solo se importa desde un componente cargado bajo demanda, un tween que use ease: 'app-entrada' antes de esa carga no encontrará el nombre y caerá al ease por defecto sin decir nada. Las curvas del proyecto se definen en la entrada de la aplicación, nunca en una rama perezosa.
Unifica el lenguaje de movimiento de un proyecto: coge los tres cubic-bezier que uses en tu CSS, créalos como CustomEase con los mismos cuatro números y nombres prefijados, y sustituye todos los eases con nombre de tus tweens por ellos. Después dibuja las tres curvas con getSVGData en una página de documentación y comprueba visualmente que coinciden con las del CSS.