Animations with GSAP + React 🌟
Tags: react, javascript, tutorial, gsap
Published: 2023-02-24 15:59:33

This time we will learn how to use the GSAP library to make animations together with React JS.

> Note: this is not a gsap tutorial, so you must have some notions of this library.
>

The purpose is that you know how to complement with good practices the animations within the React JS ecosystem.


&nbsp;

## Table of contents.
> 📌 [Technologies to be used.](#1)  
> 📌 [Creating the project.](#2)  
> 📌 [Using GSAP for the first time.](#3)  
> 📌 [Getting to know gsap.context().](#4)  
> 📌 [Using Timelines.](#5)  
> 📌 [Animating with interactions.](#6)  
> 📌 [Avoiding flash of content that is not yet styled.](#7)  
> 📌 [Conclusion.](#8)  

&nbsp;


## ☄️ Technologies to be used. <a id="1"></a>

- ▶️ React JS (v.18)
- ▶️ [GSAP 3](https://greensock.com/gsap/)
- ▶️ Vite JS
- ▶️ TypeScript
- ▶️ CSS vanilla (You can find the styles in the repository at the end of this post) 


## ☄️ Creating the project. <a id="2"></a>

We will name the project: **`gsap-react`** (optional, you can name it whatever you like).
```bash
npm init vite@latest
```
We create the project with Vite JS and select React with TypeScript.  
Then we run the following command to navigate to the directory just created.

```bash
cd gsap-react
```
Then we install the dependencies.
```bash
npm install
```
Then we open the project in a code editor (in my case VS code).
```bash
code .
```


## ☄️ Using GSAP for the first time. <a id="3"></a>

Inside the **src/App.tsx** file we delete everything and create a component that displays a div with a `hello world`.

> The **Title** component is not so important.

```tsx
const App = () => {
    return (
        <>
            <Title />
            <div className="square">Hello World</div>
        </>
    )
}
export default App
```

This is what it would look like.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwh1awea9tm89jas3hrc.gif)


Now let's install GSAP.

```bash
npm install gsap
```

To use **gsap** and animate some element we can do it in the following way...

First we import **gsap**.

Then, gsap has several functions to perform animations.

-**to**: will start at the current state of the element and animate "towards" the values defined in the interpolation.
-**from**: *is like a reverse .to(), which animates "from" the values defined in the interpolation and ends at the current state of the element.
-**fromTo**: initial and final values are defined.
-**set**: immediately sets the properties (without animation). 

In this case we will use the most common one which is the **to** function, since all functions are used in the same way with two parameters (except fromTo which receives 3).

- The first parameter is the element or elements to animate, this parameter can be a string (the id of the HTML element or even a complex CSS selector) or it can be an HTML element or an array of HTML elements.

- The second parameter is an object with the variables that you want to animate and what value you want to give them.


```tsx
import { gsap } from "gsap";


const App = () => {
    
 gsap.to( ".square", { rotate: 360 })

  return (
    <>
      <div className="square">Hello World</div>
    </>
  )
}
export default App
```


> But stop, this you just saw, is a bad practice in React JS 😞....
>
 
First, side effects like animations should be executed inside the **useEffect** hook as a good practice to avoid unexpected behavior.

Second, note that the first parameter we put **".square"**, which is not bad, it would even work but, React recommends us not to access DOM elements in that way, for that we would use another hook, which is the **useRef**.

Then this would be the code of our first animation, when the app starts, the animation will be executed and it should rotate in 360 degrees the div with the Hello World. <a id="first-animation"></a>

```tsx
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
```

## ☄️ Getting to know gsap.context(). <a id="4"></a>

Well, we already saw how to make an animation, using the good practices. But what if we want to make other animations to different elements, do we have to create more references? Well it is not necessary, thanks to **gsap.context**.

Basically, you just create a reference, which will act as a parent, containing the elements you want to animate.

The function gsap.context has two parameters.

- A callback, where you will basically perform the animations, (keep in mind that you will only select the descendant elements).

- The element that will act as container.

But now let's go step by step:

1 - We have defined this component


```tsx
const App = () => {

  return (
    <div>
      <div className="square">Hello World</div>
    </div>
  )
}
export default App
```

2 - We create a new reference to the parent element that encloses all the other elements.

```tsx
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
```

3 - Create an effect. 

```tsx
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
```

4 - Inside the effect, we create a new variable, to which we will assign the context function of gsap. This is because at the end we will need to clean the animations and that they do not continue executing, for that reason we will create the variable **ctx** that we will use later.

```tsx
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
```

5 - The **context** function receives two parameters, the callback (where we will set the other animations), and the element (this element must contain descendant elements).

> Note that the second parameter we are passing all the reference as such, **We do not pass the app.current**.

At once we execute the useEffect cleanup function. and we use the variable ctx that we declared previously and we execute the method **revert()** to clean up the animations so that they are not executed.

```tsx
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
```

6 - Now we can perform the animations inside the callback defined in the **context** method.

Let's make an animation as [I showed you before](#first-animation)

If you notice the animation inside the context callback, it is somewhat contradictory to the best practices I mentioned before, since we get the element back through its class. 🤔

**But the advantage is that you won't have to create a reference for each element you want to animate** 😉

```tsx
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
```

> Note that if you want to animate an element (**.square2**) that is not a descendant of the referenced parent element, **you will not be able to animate it in the context, unless you move the element into the referenced element so that it becomes a child element**.
>

The element with the .square2 class will not animate because it is not a child element of the element that has the **ref**.

```tsx
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>
```

## ☄️ Using Timelines. <a id="5"></a>
Timelines will help us to create animations in sequence.

And to create a timeline, we will do it in the following way.

We have practically all the previous code, where we use the context function of gsap.
Only that the callback for the moment is empty, and we add a new div element with the class **square2** and inside a "Hello World 2".

```tsx
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
```

To create the timeline, we will use a new reference.

```tsx
  const tl = useRef<GSAPTimeline>()
```

Now inside the callback of the **context**, to the ref **tl** we will assign the function timeline of gsap.

This will help us to avoid creating a new timeline each time the component is rendered.

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

    tl.current = gsap.timeline()

}, app);
```

So, to add an animation to an element, we just execute the animation type with its required parameters.

As follows:

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

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

}, app);
```

For a better code reading we can place the code like this:

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

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

}, app);
```

This means that first the animation is executed to the element with the class **.square** and until this animation finishes the next animation will be executed which is for the element with the class **.square2**.

Of course, the duration of the animation can be configured so that they are executed at the same time or something like that.

The code would look like this:

```tsx
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
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0g3m53y82pn39cewfsv.gif)


## ☄️ Animating with interactions. <a id="6"></a>

You can also execute the animations through interactions that are made with the elements, not only when starting your application and in the useEffect.

For example, by means of the event click to an element you can trigger some animation.

Note that the function handleClick receives the event. For which we will have access to the element that we are giving click.

And that element (**e.target**) is what we will pass to the animation **"to "** of **gsap**.

```tsx
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
```

You can do this not only with a click event, but also with several other events such as onMouseEnter or onMouseLeave.

```tsx
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
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awrpvi0kemokju1n2hlh.gif)


## ☄️ Avoiding flash of content that is not yet styled. <a id="7"></a>

Surely you have noticed that when you start the application and you must execute an animation at the beginning, you notice a small flash of the content that you want to animate but without css styles, after that the corresponding animation is already executed.

Along this path you have seen that we use useEffect to execute the animations when the application starts. But useEffect **is executed after the DOM has been painted** and that's why that little unwanted flash occurs.

To avoid that flash, instead of using useEffect we will use **useLayoutEffect** which works exactly the same as useEffect but the difference is that the useLayoutEffect **is executed before the DOM is painted**.

This is an example taken from the gsap documentation, and is exactly what we want to avoid.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n05gisrbpk9t857wy6zq.gif)


We avoid this by using **useLayoutEffect**


```tsx
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
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsuyjw60bleul9s8b67v.gif)


## ☄️ Conclusion. <a id=""></a>

The GSAP library is certainly very interesting, so I hope I have helped you to understand how to use it with React JS following good practices and tips. 🙌

**By the way another tip would be to not animate everything. Also you should only animate properties like transformations or opacity and avoid animating properties like filter or boxShadow as they consume a lot of CPU in the browsers**.

Make sure the animations work on low end devices.

If you know any other different or better way to make animations with this library or another, feel free to comment it.


> **I invite you to check my portfolio in case you are interested in contacting me for a project!. [Franklin Martinez Lucas](https://franklin-dev.netlify.app/)**

> 🔵 Don't forget to follow me also on twitter: [**@Frankomtz361**](https://twitter.com/Frankomtz361)

{%embed https://twitter.com/Frankomtz361/status/1575330172819193857?s=20&t=AVAVQ1am_AkFb2TyMo9S-A %}