DEV Community

Cover image for Animaciones con GSAP + React 馃専
Franklin Martinez
Franklin Martinez

Posted on

Animaciones con GSAP + React 馃専

En esta ocasi贸n aprenderemos como usar la librer铆a de GSAP para realizar animaciones junto con React JS.

Nota: no es un tutorial de gsap, por lo que debes tener ciertas nociones de esta librer铆a.

El prop贸sito es que sepas complementar con buenas practicas las animaciones dentro del ecosistema de React JS.

Tabla de contenido.

馃搶 Tecnolog铆as a utilizar.

馃搶 Creando el proyecto.

馃搶 Usando GSAP por primera vez.

馃搶 Conociendo gsap.context().

馃搶 Usando lineas de tiempo.

馃搶 Animar con interacciones.

馃搶 Evitar el flash del contenido que aun no esta estilizado.

馃搶 Conclusi贸n.

鈽勶笍 Tecnolog铆as a utilizar.

  • 鈻讹笍 React JS (version 18)
  • 鈻讹笍 GSAP 3
  • 鈻讹笍 Vite JS
  • 鈻讹笍 TypeScript
  • 鈻讹笍 CSS vanilla (Los estilos los encuentras en el repositorio al final de este post)

鈽勶笍 Creando el proyecto.

Al proyecto le colocaremos el nombre de: gsap-react (opcional, tu le puedes poner el nombre que gustes).

npm init vite@latest
Enter fullscreen mode Exit fullscreen mode

Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.

Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

cd gsap-react
Enter fullscreen mode Exit fullscreen mode

Luego instalamos las dependencias.

npm install
Enter fullscreen mode Exit fullscreen mode

Despu茅s abrimos el proyecto en un editor de c贸digo (en mi caso VS code).

code .
Enter fullscreen mode Exit fullscreen mode

鈽勶笍 Usando GSAP por primera vez.

Dentro del archivo src/App.tsx borramos todo y creamos un componente que muestre un div con un hello world

El componente Title no es tan importante

const App = () => {
    return (
        <>
            <Title />
            <div className="square">Hello World</div>
        </>
    )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Asi se ver铆a.

Image description

Ahora instalemos GSAP.

npm install gsap
Enter fullscreen mode Exit fullscreen mode

Para usar gsap y animar alg煤n elemento podemos hacerlo de la siguiente manera...

Primero importamos gsap.

Luego, gsap tiene varias funciones para realizar animaciones.

-to: comenzar谩 en el estado actual del elemento y animar谩 "hacia" los valores definidos en la interpolaci贸n.
-from: es como un .to() al rev茅s, que anima "desde" los valores definidos en la interpolaci贸n y termina en el estado actual del elemento.
-fromTo: Se definen los valores iniciales y finales.
-set: establece inmediatamente las propiedades (sin animaci贸n).

En este caso usaremos el m谩s com煤n que es la funci贸n to, ya que todas las funciones se usan de la misma manera con dos par谩metros (a excepci贸n del fromTo que recibe 3).

  • El primer par谩metro es el elemento o elementos a animar, este par谩metro puede ser un string (el id del elemento HTML o incluso hasta un selector complejo de CSS) o puede ser un elemento HTML o un array de elementos HTML.

  • El segundo par谩metro es un objeto con las variables que quieres animar y que valor les quieres dar.

import { gsap } from "gsap";


const App = () => {

 gsap.to( ".square", { rotate: 360 })

  return (
    <>
      <div className="square">Hello World</div>
    </>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Pero detente, esto que acabas de ver, es una mala practica en React JS 馃槥...

Primero, los efectos secundarios como las animaciones, deben ser ejecutadas dentro del hook useEffect como buena practica y asi evitar comportamientos no esperados.

Segundo, nota que el primer par谩metro colocamos ".square", lo cual no esta mal, incluso funcionaria pero, React nos recomienda el no acceder a elementos del DOM de esa manera, para eso usar铆amos otro hook, que es el useRef.

Entonces asi quedar铆a el c贸digo de nuestra primera animaci贸n, cuando la app comience, se ejecutara la animaci贸n que deber铆a rotar en 360 grados el div con el Hello World.

import { gsap } from "gsap";
import { useEffect, useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);


  useEffect(() => {

    gsap.to(app.current, { rotate: 360, duration: 5 })

  }, [])

  return (
    <>
      <div ref={app}>Hello World</div>
    </>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

鈽勶笍 Conociendo gsap.context().

Bueno ya vimos como hacer una animaci贸n, usando las buenas practicas. Pero que tal si queremos hacer otras animaciones a distintos elementos, 驴tenemos que crear mas referencias?. Pues no es necesario, gracias a gsap.context.

B谩sicamente, solo creas una referencia, que actuara como padre, conteniendo los elementos que quieras animar.

La funci贸n gsap.context tiene dos par谩metros.

  • Un callback, donde b谩sicamente realizaras las animaciones, (ten en cuanta que solo seleccionaras los elementos descendientes).

  • El elemento que actuara como contenedor.

Pero ahora vamos paso a paso:

1 - Tenemos definido este componente

const App = () => {

  return (
    <div>
      <div className="square">Hello World</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

2 - Creamos una nueva referencia hacia el elemento padre que encierra todos los dem谩s elementos.

import { useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);

  return (
    <div ref={app}>
      <div className="square">Hello World</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

3 - Creamos un efecto.

import { gsap } from "gsap";
import { useEffect, useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);

  useEffect(() => {


  }, [])


  return (
    <div ref={app}>
      <div className="square">Hello World</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

4 - Dentro del efecto, creamos una nueva variable, a la cual le asignaremos la funci贸n context de gsap. Esto es porque al final necesitaremos limpiar las animaciones y que no se sigan ejecutando, por eso crearemos la variable ctx que usaremos mas adelante.

import { gsap } from "gsap";
import { useEffect, useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);

  useEffect(() => {

    let ctx = gsap.context()

  }, [])


  return (
    <div ref={app}>
      <div className="square">Hello World</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

5 - La funci贸n context recibe dos par谩metros, el callback (donde estableceremos las dem谩s animaciones), y el elemento (este elemento debe contener elementos descendientes).

Nota que el segundo par谩metro le estamos pasando toda la referencia como tal, No le pasamos el app.current

De una vez ejecutamos la funci贸n de limpieza del useEffect. y usamos la variable ctx que declaramos anteriormente y ejecutamos el m茅todo revert() para limpiar las animaciones y que no se ejecuten

import { gsap } from "gsap";
import { useEffect, useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);

  useEffect(() => {

    let ctx = gsap.context(() => {}, app);

    return () => ctx.revert();

  }, [])


  return (
    <div ref={app}>
      <div className="square">Hello World</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

6 - Ahora si, podemos realizar las animaciones dentro del callback definido en m茅todo context

Realizamos una animaci贸n como te lo mostr茅 anteriormente

Si notas la animaci贸n dentro de callback del context, es algo contradictorio a las buenas practicas que mencione antes, ya que volvemos obtener el elemento mediante su clase. 馃

Pero la ventaja es que no tendr谩s que crear una referencia por cada elemento que quieras animar 馃槈

import { gsap } from "gsap";
import { useEffect, useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);

  useEffect(() => {

    let ctx = gsap.context(() => {

      gsap.to(".square", { rotate: 360, duration: 5 });
      //gsap.to(".square2", { rotate: 360, duration: 5 });
      //gsap.to(".square3", { rotate: 360, duration: 5 });

    }, app);

    return () => ctx.revert();

  }, [])


  return (
    <div ref={app}>
      <div className="square">Hello World</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Nota que si tu quieres animar un elemento (.square2) que no es descendiente del elemento padre que tiene la referencia, no podr谩s animarlo en el context, al menos que muevas dicho elemento dentro del elemento con la referencia para que se convierta en un elemento hijo.

El elemento con la clase .square2 no se animara porque no es un elemento hijo del elemento que tiene la ref

useEffect(() => {

let ctx = gsap.context(() => {

    gsap.to(".square", { rotate: 360, duration: 5 });

    gsap.to(".square2", { rotate: 360, duration: 5 }); // Don't work 鉂

}, app);

return () => ctx.revert();

}, [])

<div ref={app}>
    <div className="square">Hello World</div>
</div>

<div className="square2">Hello World</div>
Enter fullscreen mode Exit fullscreen mode

鈽勶笍 Usando lineas de tiempo.

Las lineas de tiempo nos ayudaran a crear animaciones en secuencia.

Y para crear una linea de tiempo, lo haremos de la siguiente manera.

Tenemos pr谩cticamente todo el c贸digo anterior, donde usamos la funci贸n context de gsap.
Solamente que el callback por el momento esta vaci贸, y agregamos un nuevo elemento div con la clase square2 y dentro un Hello World 2

import { gsap } from "gsap";
import { useEffect, useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);

  useEffect(() => {

    let ctx = gsap.context(() => {}, app);

    return () => ctx.revert();

  }, [])


  return (
    <div ref={app}>
      <div className="square">Hello World</div>
      <div className="square2">Hello World 2</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Para crear la linea del tiempo, usaremos una nueva referencia.

  const tl = useRef<GSAPTimeline>()
Enter fullscreen mode Exit fullscreen mode

Ahora dentro del callback del context, a la ref tl le asignaremos la funci贸n timeline de gsap.

Esto nos servir谩 para evitar crear una nueva linea de tiempo cada vez que se renderize el componente.

let ctx = gsap.context(() => {

    tl.current = gsap.timeline()

}, app);
Enter fullscreen mode Exit fullscreen mode

Entonces, para agregar una animaci贸n a un elemento, solamente ejecutamos el tipo de animaci贸n con sus par谩metros requeridos.

De la siguiente manera:

let ctx = gsap.context(() => {

    tl.current = gsap.timeline().to(".square", { rotate: 360 }).to(".square2", { x: 200});

}, app);
Enter fullscreen mode Exit fullscreen mode

Para una mejor lectura de c贸digo podemos colocar el c贸digo asi:

let ctx = gsap.context(() => {

    tl.current = gsap.timeline()
      .to(".square", { rotate: 360 })
      .to(".square2", { x: 200 })

}, app);
Enter fullscreen mode Exit fullscreen mode

Esto significa que primero se ejecuta la animaci贸n al elemento con la clase .square y hasta que esta animaci贸n termine se ejecutara la siguiente animaci贸n que es para el elemento con la clase .square2.

Claro que la duraci贸n de la animaci贸n puede configurarse para que se ejecuten al mismo tiempo o algo por el estilo.

El c贸digo quedar铆a asi:

import { gsap } from "gsap";
import { useEffect, useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);
  const tl = useRef<GSAPTimeline>()

  useEffect(() => {

    let ctx = gsap.context(() => {

      tl.current = gsap.timeline()
        .to(".square", { rotate: 360 })
        .to(".square2", { x: 200 });

    }, app);

    return () => ctx.revert()

  }, [])


  return (
    <div ref={app}>
      <div className="square">Hello World</div>
      <div className="square2">Hello World 2</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Image description

鈽勶笍 Animar con interacciones.

Tambi茅n puedes ejecutar las animaciones mediante interacciones que se hagan con los elementos, no solamente al iniciar tu aplicaci贸n y en el useEffect.

Por ejemplo, mediante el evento click a un elemento puedes disparar alguna animaci贸n.

Nota que la funci贸n handleClick recibe el evento. Por lo cual tendremos acceso al elemento que le estamos dando click.

Y ese elemento (e.target) es lo que le pasaremos a la animaci贸n "to" de gsap

import { gsap } from "gsap";

const App = () => {

  const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {

    gsap.to(e.target, { rotation: '50', yoyo: true, repeat: 1 })
  }

  return (
    <>
      <div onClick={handleClick} >Hello World</div>
    </>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

No solamente lo puedes hacer con un evento click, tambi茅n con otros diversos eventos como onMouseEnter u onMouseLeave

import { gsap } from "gsap";

const App = () => {

  const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    gsap.to(e.target, { rotation: '50', yoyo: true, repeat: 1 })
  }

  const onEnter = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    gsap.to(e.target, { scale: 1.2 });
  };

  const onLeave = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    gsap.to(e.target, { scale: 1 });
  };

  return (
    <>
      <div 
        onMouseEnter={onEnter} 
        onMouseLeave={onLeave} 
        onClick={handleClick}

      >Hello World</div>
    </>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Image description

鈽勶笍 Evitar el flash del contenido que aun no esta estilizado.

Seguramente haz notado que al iniciar la aplicaci贸n y debes ejecutar una animaci贸n al inicio, notas un peque帽o destello del contenido que quieres animar pero sin estilos css, despu茅s de eso ya se ejecuta la animaci贸n correspondiente.

A lo largo de este trayecto has visto que usamos useEffect para ejecutar las animaciones cuando inicia la aplicaci贸n. Pero useEffect se ejecuta despu茅s de que se haya pintado el DOM y es por eso que ocurre ese peque帽o destello no deseado.

Para evitar ese destello, en vez de usar useEffect usaremos useLayoutEffect el cual funciona exactamente igual que useEffect pero la diferencia es que el useLayoutEffect se ejecuta antes de que se pinte el DOM.

Este es un ejemplo tomado de la documentaci贸n de gsap, y es exactamente lo que queremos evitar.

Image description

Lo evitamos usando useLayoutEffect

import { gsap } from "gsap";
import { useLayoutEffect, useRef } from "react";

const App = () => {

  const app = useRef<HTMLDivElement>(null);

  useLayoutEffect(() => {

    let ctx = gsap.context(() => {

      gsap.to(".square", { rotate: 360, duration: 5 });

    }, app);

    return () => ctx.revert();

  }, [])


  return (
    <div ref={app}>
      <div className="square">Hello World</div>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

Image description

鈽勶笍 Conclusi贸n.

La librer铆a de GSAP, sin duda es muy interesante, por lo que espero haberte ayudado a entender como se usa con React JS siguiendo buenas practicas y consejos. 馃檶

Por cierto otro consejo seria que no animaras todo. Tambi茅n solo deber铆as animar propiedades como las transformaciones u opacidad y evita animar las propiedades como filter o boxShadow ya que consumen mucha CPU en los navegadores.

Aseg煤rate que las animaciones funcionen en dispositivos de gama baja.

Si conoces alguna otra forma distinta o mejor de realizar animaciones con esta librer铆a u otra, con gusto puedes comentarla.

Te invito a que revises mi portafolio en caso de que est茅s interesado en contactarme para alg煤n proyecto! Franklin Martinez Lucas

馃數 No olvides seguirme tambi茅n en twitter: @Frankomtz361

Latest comments (1)

Collapse
 
flash010603 profile image
Usuario163

Que buena librer铆a la de GSAP! buen post!