DEV Community

Cover image for Animations with GSAP + React 🌟
Franklin Martinez
Franklin Martinez

Posted on

Animations with GSAP + React 🌟

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.

Β 

Table of contents.

πŸ“Œ Technologies to be used.

πŸ“Œ Creating the project.

πŸ“Œ Using GSAP for the first time.

πŸ“Œ Getting to know gsap.context().

πŸ“Œ Using Timelines.

πŸ“Œ Animating with interactions.

πŸ“Œ Avoiding flash of content that is not yet styled.

πŸ“Œ Conclusion.

Β 

β˜„οΈ Technologies to be used.

  • ▢️ React JS (v.18)
  • ▢️ GSAP 3
  • ▢️ Vite JS
  • ▢️ TypeScript
  • ▢️ CSS vanilla (You can find the styles in the repository at the end of this post)

β˜„οΈ Creating the project.

We will name the project: gsap-react (optional, you can name it whatever you like).



npm init vite@latest


Enter fullscreen mode Exit fullscreen mode

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.



cd gsap-react


Enter fullscreen mode Exit fullscreen mode

Then we install the dependencies.



npm install


Enter fullscreen mode Exit fullscreen mode

Then we open the project in a code editor (in my case VS code).



code .


Enter fullscreen mode Exit fullscreen mode

β˜„οΈ Using GSAP for the first time.

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.



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


Enter fullscreen mode Exit fullscreen mode

This is what it would look like.

Image description

Now let's install GSAP.



npm install gsap


Enter fullscreen mode Exit fullscreen mode

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.



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

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.



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

β˜„οΈ Getting to know gsap.context().

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



const App = () => {

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


Enter fullscreen mode Exit fullscreen mode

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



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 - Create an effect.



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 - 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.



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 - 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.



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 - Now we can perform the animations inside the callback defined in the context method.

Let's make an animation as I showed you before

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 πŸ˜‰



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

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.



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

β˜„οΈ Using Timelines.

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".



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

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



  const tl = useRef<GSAPTimeline>()


Enter fullscreen mode Exit fullscreen mode

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.



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

    tl.current = gsap.timeline()

}, app);


Enter fullscreen mode Exit fullscreen mode

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

As follows:



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

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

}, app);


Enter fullscreen mode Exit fullscreen mode

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



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

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

}, app);


Enter fullscreen mode Exit fullscreen mode

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:



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

β˜„οΈ Animating with interactions.

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.



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

You can do this not only with a click event, but also with several other events such as onMouseEnter or 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

β˜„οΈ Avoiding flash of content that is not yet styled.

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

We avoid this by using 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

β˜„οΈ Conclusion.

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

πŸ”΅ Don't forget to follow me also on twitter: @Frankomtz361

Top comments (7)

Collapse
 
flash010603 profile image
Usuario163

I really like this animation library, thanks for the information.

Collapse
 
gabrieleromeo profile image
Gabriele Romeo

Great introduction. Thanks!

Since in your example you named the project there is a little mistake in the initialization:
npm init vite@latest should be
npm init vite@latest gsap-react

Instead of doing npm init... my-project-name and cd my-project-name you could simply do in one line npm init vite@latest gsap-react && cd $_

$_ expands to the last argument to the previous command

Collapse
 
valeryia7719 profile image
Valeryia Klopava

Hello! I found a lot of tutorial how to use gsap in this way, but I can't find some articles about react-gsap. May be someone worked with this library? I can't use Scrolltrigger there.

Collapse
 
halltech profile image
Olajide Ayomide

Please is there a resource I could use to explore this animation library apart from the documentation

Collapse
 
livelong_ponder profile image
Live Long & Ponder

Thanks for the info! This was super insightful and really informative.

Collapse
 
techipradeep profile image
Pradeep kumar

I love this article. Nice explanation to use effectively gsap in react . Could you tell which one is more effective in react framer motion, GSAP, and Three.js .

Collapse
 
sumankalia profile image
Suman Kumar

Great Article