wandres.dev
GSAP II · Los tweens: to, from, fromTo, set

Anatomía del objeto vars

Cómo distingue GSAP una propiedad que hay que animar de una instrucción para el motor, el catálogo completo de propiedades especiales, y los atajos de transformación que no son propiedades CSS.

⏱ 20 min

El segundo argumento de cualquier tween es un objeto plano en el que conviven dos cosas radicalmente distintas: las propiedades que quieres animar y las instrucciones sobre cómo animarlas. No hay separación sintáctica entre ambas —duration y opacity se escriben igual— y sin embargo el motor las trata de forma completamente diferente. Entender cómo hace esa distinción explica por qué animar una propiedad llamada delay de un objeto tuyo es imposible, por qué x no es una propiedad CSS, y qué ocurre con un nombre que GSAP no reconoce.

🎯 Al terminar esta lección sabrás
  • Distinguir las propiedades especiales de las propiedades animadas y explicar el mecanismo.
  • Enumerar las propiedades especiales que controlan tiempo, repetición y comportamiento.
  • Usar los atajos de transformación y saber por qué existen frente a la cadena transform.
  • Predecir qué hace GSAP con una propiedad que no puede animar.

Cómo distingue el motor

El mecanismo es una lista de nombres reservados. GSAP tiene un conjunto fijo de nombres —duration, delay, ease, repeat, onComplete y compañía— y cualquier propiedad del objeto vars cuyo nombre esté en esa lista se interpreta como instrucción. Todo lo demás se intenta animar.

La consecuencia inmediata es que esos nombres están quemados: si tu objeto tiene una propiedad llamada delay y quieres animarla, no puedes hacerlo directamente. Es un caso raro pero real —un objeto de configuración de audio con un delay, por ejemplo— y la salida es animar una propiedad intermedia y copiarla en el onUpdate.

Los plugins registrados añaden sus propios nombres a esa lista. Cuando registras ScrollTrigger, el nombre scrollTrigger pasa a ser reservado; cuando registras MorphSVG, lo hace morphSVG. Ese es el motivo por el que un plugin no registrado falla en silencio: su nombre no está en la lista, GSAP lo trata como propiedad a animar, intenta interpolar un objeto y no puede.

gsap.to('.caja', {
  // Instrucciones para el motor.
  duration: 1.2,
  ease: 'power3.out',
  delay: 0.2,
  repeat: 2,
  yoyo: true,
  onComplete: limpiar,

  // Propiedades que se animan.
  x: 400,
  rotation: 180,
  backgroundColor: '#89b4fa',
  borderRadius: '50%',
});

El catálogo de propiedades especiales

Merece la pena tenerlas todas en la cabeza, agrupadas por lo que controlan.

Tiempo y ritmo. duration en segundos, con valor por defecto 0.5. delay, también en segundos. ease, que por defecto es power1.out.

Repetición. repeat es el número de repeticiones después de la primera, así que repeat: 1 reproduce dos veces y repeat: -1 repite indefinidamente. repeatDelay es la pausa entre repeticiones. yoyo hace que las repeticiones pares vayan hacia atrás. repeatRefresh hace que el tween recalcule sus valores de partida y llegada en cada iteración completa, que es lo que necesitas si usas valores aleatorios o relativos y quieres que cambien en cada vuelta; ojo, no refresca duration, delay ni stagger.

Estado inicial. paused crea el tween sin reproducir. reversed lo crea orientado hacia atrás. immediateRender fuerza o impide el renderizado inmediato. startAt define valores de partida para propiedades aunque no se animen, lo cual es una forma de decir “colócalo así antes de empezar” sin un set aparte.

Callbacks. onStart, onUpdate, onComplete, onRepeat, onReverseComplete y onInterrupt, cada uno con su variante Params para pasarle argumentos, más callbackScope para fijar el this de todos ellos. Tienen su propia lección en este nivel.

Comportamiento. overwrite decide qué hacer con tweens en conflicto. stagger reparte los tiempos de inicio entre varios objetivos. keyframes permite encadenar varios estados dentro de un solo tween. id da un identificador para recuperarlo con gsap.getById. data es un hueco libre donde guardar lo que quieras, accesible después como tween.data. inherit: false impide que el tween herede los defaults de su timeline padre. lazy controla si GSAP retrasa la escritura de valores hasta el final del tick para evitar el vaivén de lecturas y escrituras que el navegador penaliza.

Dirección del ease al invertir. easeReverse, añadido en la versión 3.15, controla el ease cuando el cabezal cambia de dirección. Sin él, reproducir hacia atrás recorre la curva del revés, de modo que un power2.out se comporta como un power2.in al invertir. Con easeReverse: true se usa el mismo ease también hacia atrás; con un ease concreto, se usa ese otro. GSAP recalcula el easing desde el punto exacto donde el cabezal cambió de dirección, así que funciona también en mitad del tween. Sustituye al antiguo yoyoEase, que quedó obsoleto en esa misma versión.

gsap.to('.panel', {
  x: 300,
  duration: 0.8,
  ease: 'power3.out',
  easeReverse: true,   // al invertir, misma sensacion, no la curva del reves
});
ℹ️
El valor por defecto de duración no es cero

Omitir duration no crea un tween instantáneo: crea uno de medio segundo. Los tweens de duración cero se crean con gsap.set o con duration: 0 explícito. Es un error frecuente al construir timelines, donde un paso al que se le olvidó la duración introduce medio segundo de retraso en toda la secuencia que cuesta encontrar.

Los atajos de transformación

La otra mitad del objeto vars son las propiedades animadas, y ahí GSAP introduce nombres que no existen en CSS. No son alias cosméticos: cambian el modelo.

GSAP Equivalente CSS
x, y, z translateX, translateY, translateZ en píxeles
xPercent, yPercent translateX, translateY en porcentaje
scale, scaleX, scaleY scale y sus ejes
rotation, rotationX, rotationY rotate en grados y las rotaciones 3D
skew, skewX, skewY skew en grados
transformOrigin, svgOrigin transform-origin en espacio local o en el del SVG
transformPerspective, perspective perspectiva propia del elemento o del contenedor
autoAlpha opacity más visibility

La razón de que existan es doble. La primera es de rendimiento: cuando escribes transform como cadena, GSAP tiene que aplicarla, leer de vuelta la matriz que el navegador ha calculado y descomponerla, porque una cadena puede contener cualquier número de transformaciones en cualquier orden. Con x: 50 no hay ninguna de esas dos operaciones.

La segunda es de coherencia. En CSS el orden de las funciones dentro de transform importa, y eso significa que dos animaciones que quieran tocar componentes distintos de la misma cadena se pisan inevitablemente. GSAP mantiene cada componente por separado en una caché y compone siempre en el mismo orden —traslación, escala, rotación en x, rotación en y, sesgado, rotación en z— de modo que un tween sobre x y otro sobre rotation pueden correr a la vez sin conflicto.

autoAlpha merece mención aparte. Es opacity con una regla añadida: al llegar a cero pone visibility: hidden, y al subir de cero la devuelve a inherit. Con eso, un elemento invisible deja de recibir clics y deja de costarle trabajo al compositor, cosas que opacity: 0 a secas no consigue. Y hay un detalle muy útil: si el elemento parte con visibility: hidden y opacity: 1, GSAP asume que la opacidad debe partir de cero, que es exactamente el patrón para evitar el destello inicial.

Además, las rotaciones aceptan sufijos de dirección: "_cw" fuerza sentido horario, "_ccw" antihorario y "_short" elige el camino más corto. Rotar de 170 a menos 170 grados por el camino corto son 20 grados en horario en vez de 340 en antihorario.

gsap.to('.aguja', { rotation: '-170_short', duration: 0.8 });

Qué hace GSAP con lo que no puede animar

Si declaras una propiedad no interpolable —position: 'absolute', borderStyle: 'solid'— GSAP no falla: la aplica de golpe al principio del tween. La excepción es display: 'none', que se aplica al final por razones evidentes.

Es un comportamiento cómodo y también una trampa silenciosa: una propiedad mal escrita —opactiy en vez de opacity— no produce ningún error. GSAP intenta escribirla, el navegador la ignora, y la animación corre sin hacer nada visible. No hay forma de que el motor sepa que era una errata.

Las unidades siguen una regla sensata: si das un número desnudo se usa la unidad por defecto de esa propiedad, que es píxeles para casi todo y grados para las rotaciones. Si das una cadena con unidad, se usa esa. Y si el valor actual está en una unidad distinta del destino, GSAP convierte, de modo que animar de un porcentaje a una medida en píxeles funciona.

gsap.to('.barra', {
  width: '75%',        // interpola en porcentaje
  x: '20vw',           // unidad explicita
  rotation: '1.25rad', // radianes en vez de grados
  duration: 1,
});
La propiedad especial que más gente ignora es startAt

startAt resuelve un problema que casi todo el mundo resuelve mal. La situación es esta: quieres animar un elemento de A a B, pero además necesitas que ciertas propiedades que no se animan estén en cierto estado desde el principio del tween. La solución instintiva es un gsap.set justo antes, y es incorrecta en dos casos que importan.

Dentro de una timeline, el set previo se ejecuta cuando el cabezal llega a su posición, sí, pero si rebobinas la timeline el set no se deshace: es un tween de duración cero y al invertirse vuelve a poner lo mismo. El estado inicial se pierde al rebobinar.

Con un tween que se repite, el set previo se ejecutó una sola vez y no se vuelve a aplicar en cada iteración.

startAt no tiene ninguno de los dos problemas porque forma parte del propio tween: define el estado del que parte y, por tanto, se restaura correctamente al invertir y se reaplica en cada repetición. Cuando veas un gsap.set inmediatamente antes de un tween sobre el mismo objetivo, casi siempre debería ser un startAt dentro del tween.

⚔️ Reto práctico

Comprueba la diferencia entre set previo y startAt: construye una timeline con un elemento que parte de scale: 0 y crece, hecha de las dos formas. Reproduce, rebobina con reverse() y vuelve a reproducir. Con la versión de set el elemento no volverá a partir de cero; con startAt sí.