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
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
Then we install the dependencies.
npm install
Then we open the project in a code editor (in my case VS code).
code .
βοΈ 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
This is what it would look like.
Now let's install GSAP.
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.
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.
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().
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
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
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
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
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
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
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>
βοΈ 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
To create the timeline, we will use a new reference.
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.
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:
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:
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:
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
βοΈ 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
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
βοΈ 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.
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
βοΈ 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)
I really like this animation library, thanks for the information.
Great introduction. Thanks!
Since in your example you named the project there is a little mistake in the initialization:
npm init vite@latest
should benpm init vite@latest gsap-react
Instead of doing
npm init... my-project-name
andcd my-project-name
you could simply do in one linenpm init vite@latest gsap-react && cd $_
$_
expands to the last argument to the previous commandHello! 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.
Please is there a resource I could use to explore this animation library apart from the documentation
Thanks for the info! This was super insightful and really informative.
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 .
Great Article