wandres.dev
GSAP IV · Eases y el visualizador

Qué es un ease en GSAP

Un ease es una función de progreso a progreso: cómo se nombran, qué significan in, out e inOut, por qué escribir power2 a secas no da lo que crees, y cómo registrar los tuyos.

⏱ 17 min

Un ease en GSAP es exactamente lo que su firma dice que es: una función que recibe un número entre cero y uno y devuelve otro número, normalmente también entre cero y uno pero no necesariamente. Eso es todo. No hay un tipo especial, no hay una clase, no hay magia: cualquier función con esa forma sirve como ease, y las decenas de nombres que la librería expone son atajos para funciones concretas metidas en un mapa. Entender esa simplicidad es lo que te permite escribir eases propios, componerlos, y —lo más importante— dejar de tratarlos como un catálogo del que se elige por gusto.

🎯 Al terminar esta lección sabrás
  • Explicar la firma de un ease y usar una función propia como tal.
  • Distinguir las variantes in, out e inOut de una misma familia.
  • Predecir qué ease se aplica al escribir un nombre sin sufijo.
  • Registrar un ease propio con nombre y recuperar cualquier ease como función.

La firma

Un ease es (p: número) => número. El argumento es el progreso lineal del tween, de cero a uno. El resultado es el progreso interpolado, y en GSAP se llama ratio.

// Un ease escrito a mano: acelera de forma cuadratica.
gsap.to('.caja', {
  x: 300,
  duration: 1,
  ease: (p) => p * p,
});

Ese ejemplo funciona pegado tal cual. GSAP acepta una función directamente en ease, sin registrarla ni envolverla en nada.

El valor de retorno puede salirse del rango cero-uno, y de ahí salen los efectos de rebote y sobrepaso. Un ease que devuelve 1.12 a mitad de camino hace que el elemento esté un 12% más allá de su destino en ese instante. Es la misma idea que en la lección sobre muelles: el sobrepaso es un valor de la curva mayor que uno.

Las funciones deben cumplir dos condiciones para comportarse: devolver cero en cero y uno en uno. GSAP no las verifica. Si tu ease devuelve 0.3 en p = 0, el elemento saltará al 30% del recorrido en el primer frame, y el motor no dirá nada.

Las tres variantes de cada familia

Casi todos los eases con nombre vienen en tres versiones, y la diferencia entre ellas es dónde ocurre la parte lenta.

in empieza despacio y acelera. La velocidad es cero al principio y máxima al final.

out empieza rápido y frena. Velocidad máxima al principio, cero al final.

inOut hace las dos: arranca despacio, acelera hasta la mitad, y frena hasta parar.

La relación entre ellas es puramente algebraica. Si conoces la función in, la out es su reflejo en los dos ejes, y la inOut es media in seguida de media out reescaladas:

const easeIn    = (p) => p ** 3;
const easeOut   = (p) => 1 - (1 - p) ** 3;
const easeInOut = (p) => p < 0.5
  ? (p * 2) ** 3 / 2
  : 1 - ((1 - p) * 2) ** 3 / 2;

Esas tres líneas son literalmente lo que GSAP genera para cada familia de potencias. La lección de fondo es que no hay tres curvas: hay una y dos transformaciones, y por eso elegir entre ellas no es cuestión de gusto sino de dónde tiene que estar el énfasis.

La sintaxis es el nombre, un punto y la variante:

ease: 'power2.in'
ease: 'power2.out'
ease: 'power2.inOut'
Escribir power2 a secas no es lo que crees

Esta es la trampa mejor escondida de toda la API de eases. Cuando escribes un nombre sin sufijo, GSAP no usa la variante in ni “la neutra”: usa la variante out. Internamente, al registrar cada familia, el nombre en minúsculas se asocia directamente a la función easeOut.

ease: 'power2'    // es exactamente power2.out
ease: 'elastic'   // es exactamente elastic.out
ease: 'back'      // es exactamente back.out

Esa decisión es defendible —out es la variante correcta en la gran mayoría de las animaciones de interfaz, y por eso el ease por defecto de GSAP es power1.out— pero produce dos malentendidos concretos. El primero es escribir ease: 'expo' esperando el arranque explosivo característico de la exponencial y obtener la frenada, que es lo contrario. El segundo, y peor, es la comparación injusta: alguien prueba ease: 'power4' contra ease: 'power4.inOut', ve que se sienten distintos, y saca conclusiones sobre la potencia cuando lo que ha cambiado es la variante.

La disciplina es simple: escribe siempre el sufijo. Cuesta cuatro caracteres, documenta la intención, y elimina la ambigüedad para quien lea el código después.

El catálogo, y de dónde sale cada pieza

Las familias que están en el núcleo son none, power1 a power4, sine, circ, expo, back, elastic, bounce y steps. Los nombres antiguos siguen valiendo como alias: quad es power1, cubic es power2, quart es power3, y tanto quint como strong son power4. linear y none son el mismo ease, el que no hace nada.

Fuera del núcleo, en el fichero EasePack, viven rough, slow y expoScale. Y como paquetes independientes, CustomEase, CustomBounce y CustomWiggle. Los tres grupos se cargan e importan por separado, lo cual importa porque un ease que no está cargado no da error: GSAP no lo encuentra en el mapa y cae al ease por defecto, en silencio.

import { gsap } from 'gsap';
import { EasePack } from 'gsap/EasePack';
import { CustomEase } from 'gsap/CustomEase';

gsap.registerPlugin(EasePack, CustomEase);

Recuperar y registrar eases

gsap.parseEase(nombre) devuelve la función correspondiente a un nombre. Es la puerta para usar las curvas de GSAP fuera de un tween:

const suave = gsap.parseEase('power3.inOut');

// Ahora es una funcion normal: sirve para interpolar lo que quieras.
console.log(suave(0.25).toFixed(4));  // 0.0313
console.log(suave(0.5).toFixed(4));   // 0.5000, por simetria

// Util para dibujar la curva y verla.
for (let i = 0; i <= 10; i++) {
  const p = i / 10;
  console.log(p.toFixed(1), suave(p).toFixed(3));
}

También acepta las cadenas con configuración: gsap.parseEase('back.out(2.5)') devuelve la función con ese sobrepaso.

En el sentido contrario, gsap.registerEase(nombre, funcion) mete una función tuya en el mapa con un nombre, y a partir de ahí se usa como cualquier otro ease:

// Un ease con tres escalones desiguales.
gsap.registerEase('escalones', (p) => {
  if (p < 0.3) return p * 0.5;
  if (p < 0.7) return 0.15 + (p - 0.3) * 2;
  return 0.95 + (p - 0.7) * (0.05 / 0.3);
});

gsap.to('.caja', { x: 400, duration: 1.2, ease: 'escalones' });

Registrar eases con nombre es la forma correcta de tener un lenguaje de movimiento propio en un proyecto: defines tres o cuatro curvas en el módulo central, les pones nombres del dominio —entrada, salida, enfasis— y el resto del código usa esos nombres. Cambiar la sensación de toda la aplicación es entonces editar un fichero.

⚠️
No reutilices nombres del catálogo

Registrar un ease llamado power2 o expo sobrescribe el del catálogo para toda la aplicación. Es legal y prácticamente nunca es lo que quieres: el resto del código sigue escribiendo power2 esperando la curva de siempre. Usa nombres de tu dominio, no del catálogo.

Componer eases

Como los eases son funciones, se componen como funciones. Dos patrones que aparecen a menudo.

Invertir un ease es evaluarlo del revés:

const invertir = (ease) => (p) => 1 - ease(1 - p);
const miIn = invertir(gsap.parseEase('power3.out'));   // equivale a power3.in

Encadenar dos eases aplica uno sobre el resultado del otro, lo cual produce curvas que no están en el catálogo:

const componer = (a, b) => (p) => b(a(p));
gsap.registerEase('doble', componer(
  gsap.parseEase('power2.in'),
  gsap.parseEase('sine.out'),
));

Y el patrón más útil de todos: usar el ease como fuente de un valor propio dentro de un onUpdate, aprovechando la propiedad ratio del tween, que es el progreso ya pasado por la curva.

gsap.to({}, {
  duration: 1,
  ease: 'elastic.out(1, 0.4)',
  onUpdate() {
    // this.ratio puede pasar de 1: eso es el rebote.
    pintarBarra(this.ratio);
  },
});
⚔️ Reto práctico

Dibuja las curvas: crea un <canvas> y una función que reciba un nombre de ease, lo recupere con gsap.parseEase, lo muestree en doscientos puntos y lo trace. Pinta encima power1.out, power4.out y sine.out con colores distintos y observa cuánto se parecen en la mitad final y cuánto difieren en el arranque. Es el visualizador que necesitas para el resto de este nivel.