This first post will cover how I use GSAP with the useEffect
and useRef
hooks to create animations that start on events like click or hover. As an example for us to walk through together, I'll use this animation I made for my portfolio site. Hover over it to trigger the onMouseEnter
event (Or tap on mobile to trigger onClick
).
By the end of part 2 we will have covered everything needed to make this animation.
If you don't have GSAP yet, you'll want to visit the GreenSock docs and follow the simple installation instructions. I used to just paste the CDN into the <Head>
component, but I've since opted to download the library and place it at the bottom, near my closing </body>
tag. Before, I'd occassionally have issues with the library not loading when using the CDN.
*Note: All the SVG art I use is available for free at illlustrations.co, a cool site by an artist whose work I used throughout my portfolio site.
Before we get into using GSAP in React, I'll say a few quick things about SVGs.
You might already be aware of some of the benefits of using SVGs, such as their ability to scale without losing quality or increasing in file size. But, my other favorite thing about them, which I don't see mentioned very often, is that I can go into their code and manipulate the images. It's quite easy to change a color, remove part of the graphic I don't want, or add a class for some CSS styles.
Here, I've simply changed a few fill
properties to brown on certain vectors, moved the eye groups on the x-axis, and deleted a few vectors at the top of the eyes.
Working with SVGs and Figma
In order to get in there and really pick an SVG apart, you'll want to use some sort of graphics software. I use Figma because it's free and the most powerful I've found. You can use it in the browser or download the desktop app here.
With Figma, it's easy to find the exact vector you want to work with by just clicking on it. On the left side of the Figma window, in the layer panel, you can rename the vector. This vector will then have an id
in the code when you export the SVG file. You can then grab it and manipulate it with GSAP or CSS. In the layers panel you can also create groups, ungroup, and layer your graphic. Groups are useful to animate something in the image that is made up of multiple vectors, like the owl's eyes and pupils above. You can have them all move together as one.
To export, Figma has an option in the bottom-right corner. Select the SVG, group, or single vector you want the code for, and then click export. There's an option to export as PNG, JPG, SVG, or PDF — choose SVG. And then if you want the id
's you've created to be in the XML, you'll need to click the ellipsis and select the "Include 'id' Attribute".
Here's a simple SVG.
And here's the XML for it with id
attributes that I added through Figma and then exported.
<svg width="40" height="32" viewBox="0 0 40 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="my-svg-group">
<path
id="greater-than"
d="M26 23L29 26L39 16L29 6L26 9L33 16L26 23Z"
fill="black"
/>
<path
id="less-than"
d="M14 9L11 6L1 16L11 26L14 23L7 16L14 9Z"
fill="black"
/>
<path
id="forward-slash"
d="M21.916 4.7041L24.087 5.2961L18.087 27.2971L15.916 26.7051L21.916 4.7041V4.7041Z"
fill="black"
/>
</g>
</svg>
Notice the id
's on each <path>
element, and even the group, or <g>
element. With GSAP, we'll be grabbing the parts we want to animate with these id
's.
Imagine an SVG with hundreds of lines, and you can see why clicking on the element in Figma and adding an id
is appealing, rather than sifting through the code.
GSAP in React
In order to avoid errors with GSAP and React, you'll want your animation code to run after the component has mounted. I'll be using functional components and the useEffect
hook. I also often use a handler function, which could be called on a click event or something else. Here, I'll be using the mouse enter event to simulate a hover effect.
In React, SVG's can be displayed a few different ways, but I find if you're going to animate one, the best approach is put it into its own component. So, an entire component for the simple SVG above would look something like this:
*Note: I'm also using Next.js, so the code snippets below are Next specific, i.e. no React import, etc.
function ExampleSVG() {
useEffect(() => {
console.log('in use effect')
}, [])
function handleMouseEnter() {
console.log('mouse entered')
}
return (
<svg
onMouseEnter={handleMouseEnter}
id="example-svg"
width="40"
height="32"
viewBox="0 0 40 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g id="my-svg-group">
<path
id="greater-than"
d="M26 23L29 26L39 16L29 6L26 9L33 16L26 23Z"
fill="black"
/>
<path
id="less-than"
d="M14 9L11 6L1 16L11 26L14 23L7 16L14 9Z"
fill="black"
/>
<path
id="forward-slash"
d="M21.916 4.7041L24.087 5.2961L18.087 27.2971L15.916 26.7051L21.916 4.7041V4.7041Z"
fill="black"
/>
</g>
</svg>
);
}
export default ExampleSVG;
Depending on how and when you want the animation to run, you'd put your code in the useEffect
hook or a handler for a click, mouse enter, or scroll-related event, etc.
This onMouseEnter
event handler creates the below animation.
function handleMouseEnter() {
gsap.to("#red-icon-group", { duration: 1, x: 45 })
}
Hover or tap to see the small animation.
If you need to learn about the basics of GSAP more in-depth, the docs page on tweens is the best place to go. But, in summary, there are a few methods you can call, such as gsap.to()
and gsap.from()
. The .to()
method takes your image to the animation settings you provide in the "vars" object (which we'll discuss below), and .from()
brings it from your provided settings.
*Note: A "tween" is like a key-frame and a term applicable to all types of animation.
The methods' first parameter selects the target you want to animate. It uses querySelectorAll()
under the hood, so you can use selectors like .class
or #id
.
The second parameter is the "vars" object, which is where the magic happens. Here, you set key-value pairs to do things like animate CSS properties and set the animation's duration. I often use "onComplete", which you set to a callback function that will run when your animation finishes. It's worth knowing that there are a lot of cool things you can do in the "vars" object, and that they're detailed in the documentation.
Using the useRef
hook
If you want to be able to run an animation more than once, for example on every click or mouse enter event, rather than just a one-time fade-in or something, then you'll have to write more code. The way I made this work was by using the useRef
hook to use the .current
property it gives us. In the .current
property we can store a mutable value, or our tween, and have access to it throughout the component's scope.
Here's a step-by-step look at how I use useRef
and GSAP together.
import styles from "./ExampleTweenComponent.module.scss";
import { useRef } from 'react'
function ExampleTweenComponent() {
const myTween = useRef(null)
return (
<svg
className={styles.svg}
xmlns="http://www.w3.org/2000/svg"
...
After importing styles, I import the useRef
hook. Within my component I call it and store its returned value into a variable, here named myTween
. Now, myTween
has a .current
property that can be accessed in the scope of useEffect
and any handlers we create.
Here's the built-out useEffect
and handleMouseEnter
functions using GSAP to create the animation.
import styles from "./ExampleTweenComponent.module.scss";
import { useRef, useEffect } from 'react'
function ExampleTweenComponent() {
const myTween = useRef(null)
useEffect(() => {
myTween.current = gsap.to("#red-icon-group", { duration: 1, x: 45 });
myTween.current.pause()
}, [])
function handleMouseEnter() {
myTween.current.restart()
}
return (
<svg
onMouseEnter={handleMouseEnter}
className={styles.svg}
xmlns="http://www.w3.org/2000/svg"
...
And here's the SVG with an animation that restarts every time the onMouseEnter
or onClick
(for mobile) event is fired.
By storing the tween in the .current
property we can control it with its methods such as .play()
, .pause()
, .resume()
, .reverse()
, .restart()
, etc.
SVG to JSX
Lastly, before moving on to more complex animations with GSAP timelines, you may have noticed warnings in the console about "Invalid DOM properties" such as 'stroke-width', accompanied by a question asking if you meant 'strokeWidth'? In fact, changing these attributes to the suggested, JSX syntax will remove the warnings.
Manually changing the names is easy enough for smaller SVGs, and even larger ones using "search" and "replace" in VSCode. But, if you want, you can use a converter. I found svg2jsx.com to be the best because it keeps the id
attributes we named in Figma. In the menu bar on the site there's a "Remove IDs" option you'll want to have switched off.
And there you have it. The basics of how I use GSAP to animate SVGs in React. Continue with part 2 to learn about making animations more complex with gsap.timeline()
.