Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...

GSAP + React, First Steps & Handy Techniques.


| GreenSock
41366

header.png

 

React is a hugely popular framework choice, and as evidenced by many of the sites in our showcase - React and GSAP can be a powerful combination. However, utilizing GSAP in React requires a different way of thinking than a vanilla JS project.

We've written this guide to help you get started using GSAP within a React project. This is not a tutorial, so feel free to dip in and out as you learn. Think of it as a collection of recommended techniques and best practices to use in your projects.

 

Why GSAP?

Animating with GSAP gives you unprecedented levels of control and flexibility. You can reach for GSAP to animate everything — from simple DOM transitions to SVG, three.js, canvas or WebGL — your imagination is the limit. More importantly, you can rely on us. We obsess about performance, optimizations and browser compatibility so that you can focus on the fun stuff. We've actively maintained and refined our tools for over a decade and there are no plans to stop. Lastly, if you ever get stuck, our friendly forum community is there to help.

Going forward we will assume a basic understanding of GSAP and React.

If you're just getting going with React, this tutorial from the React team is a great place to start.
Need a GSAP refresher? Take a break and read about tweens and timelines. We’ll be here when you get back.

Feeling confident? Skip straight to part 2 - GSAP + React, advanced animation techniques.

 

 

Online PlaygroundsOnline Playgrounds

Get started quickly by forking one of these starter templates:

 

Create a new React AppCreate a new React App

If you prefer to work locally, Create React App provides a comfortable setup for experimenting with React and GSAP.

To create a project, run:

npx create-react-app gsap-app
cd gsap-app
npm start

Once the project is set up we can install GSAP through npm,

npm i gsap
npm start

then import it into our app.

    
import React from "react";
import { gsap } from "gsap";
 
export default function App() {
 return (
   <div className="app">
     <div className="box">Hello</div>
   </div>
 );
}

More detailed information about getting started with React

Additional GSAP installation documentation

 

Targeting elementsTargeting elements

In order to animate using GSAP we need access to the element in the DOM. Refs provide a way for us to interact with and store references to DOM nodes in a React component.

const boxRef = useRef();

return <div className="box" ref={boxRef}>Hello</div>;

Read more about refs in the React docs

 

Creating our first animationCreating our first animation

GSAP updates inline style properties, so it’s important to make sure the DOM has been rendered before trying to animate anything.

If we ask GSAP to animate an element that hasn’t been rendered, we’ll get this warning in the console.

GSAP target not found.

In order to avoid targeting a null element, we can use the useEffect hook. This hook tells React that our component needs to do something after rendering.

function App() {
  // store a reference to the box div
  const boxRef = useRef();

  // wait until DOM has been rendered
  useEffect(() => {
    gsap.to(boxRef.current, { rotation: "+=360" });
  });
  
  // DOM to render
  return <div className="box" ref={boxRef}>Hello</div>;
}

In this example, React will first render the box element to the DOM, then GSAP will rotate the box 360deg.

 

Targeting descendant elementsTargeting descendant elements

gsap.utils.selector()

Creating a ref for each and every element we want to animate can add a lot of noise to our code. We can avoid this by making use of GSAP’s selector utility to easily select descendant elements.

const el = useRef();
const q = gsap.utils.selector(el);

useEffect(() => {
   // Target ALL descendants with the class of .box
  gsap.to(q(".box"), { x: 100 });
}, []);

 

Forwarding refs

gsap.utils.selector() will target all descendants in the component tree.

Within a component based system, you may need more granular control over the elements you're targeting. You can use ref forwarding to get access to specific nested elements.

 

Creating and controlling timelinesCreating a timeline

Up until now we've just used refs to store references to DOM elements, but they're not just for elements.

Refs exists outside of the render loop - so they can be used to store any value that you would like to persist for the life of a component. If you're coming from class based components, this should be familiar to you as it’s essentially the same as using ‘this’.

In order to avoid creating a new timeline on every render, it's important to create the timeline inside an effect and store it in a ref.

function App() {
  const el = useRef();
  const q = gsap.utils.selector(el);
  const tl = useRef();
      
  useEffect(() => {            
    
    tl.current = gsap.timeline()
      .to(q(".box"), {
        rotate: 360
      })
      .to(q(".circle"), {
        x: 100
      });

  }, []);
  
  return (
    <div className="app" ref={el}>
      <Box>Box</Box>
      <Circle>Circle</Circle>
    </div>
  );
}

This will also allow us to access the timeline in a different effect and toggle the timeline direction.

 

Controlling when React runs our animation.controlling when react runs our animation

By default useEffect runs both after the first render and after every update. So every time our component’s state changes, it will cause a re-render, which will run our effect again.

We can control when useEffect should run by passing in an array of dependencies. To only run once after the first render, we pass in an empty array.

// only runs after first render
useEffect(() => {
  gsap.to(q(".box-1"), { rotation: "+=360" });
}, []);

// runs after first render and every time `someProp` changes
useEffect(() => {
  gsap.to(q(".box-2"), { rotation: "+=360" });
}, [someProp]);

// runs after every render
useEffect(() => {
  gsap.to(q(".box-3"), { rotation: "+=360" });
});

 

Reacting to changes in statereacting to changes in state

Now that we know how to control when an effect fires, we can use this pattern to react to changes in our component. This is especially useful when passing down props.

function Box({ children, endX}) {
  const boxRef = useRef();

  // run when `endX` changes
  useEffect(() => {
    gsap.to(boxRef.current, {
      x: endX
    });
  }, [endX]);
  
  return (
    <div className="box" ref={boxRef}>{children}</div>
  );
}

 

Animating on interactionAnimating on interaction

Interaction is one of the most exciting things about animating on the web! In order to hook into user interactions like hover, we can use callbacks.

const onEnter = ({ currentTarget }) => {
  gsap.to(currentTarget, { backgroundColor: "#e77614" });
};

const onLeave = ({ currentTarget }) => {
  gsap.to(currentTarget, { backgroundColor: "#28a92b" });
};

return (
  <div className="box" onMouseEnter={onEnter} onMouseLeave={onLeave}>
    Hover Me
  </div>
);

 

Avoiding flash of unstyled content (FOUC)avoiding fouc

As useEffect fires after the DOM has been painted, when fading in elements you may notice an undesired flash of unstyled content.

longerfouc.gif

In order to avoid the flash, we can replace useEffect with useLayoutEffect. useLayoutEffect functions exactly the same as useEffect, but React doesn’t run it until the DOM has been painted.

useLayoutEffect is especially useful when you need to make DOM measurements, so we highly recommend it when using our ScrollTrigger and FLIP plugins.

More information about useEffect vs useLayoutEffect.

 

Cleaning UpCleaning up

It’s a good idea to return a cleanup function in your effects to kill off any running animations and anything else that could cause a memory leak, like an event listener.

This is particularly important if an animation runs for a really long time, makes use of ScrollTrigger, or changes the state in a component.

useEffect(() => {
  const animation1 = gsap.to(".box1", { rotation: "+=360" });
  
  const animation2 = gsap.to(".box2", {
    scrollTrigger: {
      ...
    }
  });

  const onMove = () => {
    ...
  };
  window.addEventListener("pointermove", onMove);
    
  // cleanup function will be called when component is removed
  return () => {
    animation1.kill();
    animation2.scrollTrigger.kill();
    window.removeEventListener("pointermove", onMove);
  };
}, []);

 

We hope this article was helpful - If you have any feedback please leave us a comment below so we can smooth out the learning curve for future animators!

Feeling confident and want to learn more? Check out our follow up article - GSAP + React, advanced animation techniques.

 

 

 

 

  • Like 7

Get an all-access pass to premium plugins, offers, and more!

Join the Club

We love seeing what you build with GSAP. Don't forget to let us know when you launch something cool.

- Team GreenSock



User Feedback

Recommended Comments

This updated article is amazing! I had no clue I should have used useRef for the master timeline!!! Solved many of my annoying questions!!! <333

  • Like 1
Link to comment
Share on other sites

This is absolutely mindblowing, I am so happy that I don't need react-transition-group anymore :)))

I got a question though, won't using .from method cause initial render glitch or flash? for example the whole dom might be visible before .from method sets some elements to opacity 0

also May you please add examples of transition between routes? For example when I go from homepage to about page, how to animate that?

Thanks for the amazing article though, I really enjoyed it ❤️ 

  • Like 1
Link to comment
Share on other sites

11 hours ago, Alixsep said:

I got a question though, won't using .from method cause initial render glitch or flash? for example the whole dom might be visible before .from method sets some elements to opacity 0

 

That's we recommended using useLayoutEffect as it will run before the DOM gets painted to the screen. The only time that won't happen is when using SSR and your app hasn't been hydrated. For that, you might need to do something similar to option 2 here.

https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85

 

11 hours ago, Alixsep said:

also May you please add examples of transition between routes? For example when I go from homepage to about page, how to animate that?

 

Yeah, we'll work on a demo for that, although it's a little more complicated because it involves keeping track of elements using state and React's Children API.

 

  • Like 1
Link to comment
Share on other sites

When I try to use gsap.utils.selector(), this error pops up "React Hook useEffect has a missing dependency: 'q'. Either include it or remove the dependency array  react-hooks/exhaustive-deps" How do I fix this?

Screenshot (14).png

Link to comment
Share on other sites

That warning is from eslint and won't cause any problems. If you don't want to see the warning, add the eslint-disable-next-line comment above the array, or declare q inside your use effect.

 

useEffect(() => {
	const q = gsap.utils.selector(...);
}, []);

 

Also, using logo1 as the selector context isn't going to work as it has no children. You should use a ref on the root element in your component. Please look at the examples provide above.

 

Link to comment
Share on other sites

Everything working like a charm. Just not 100% on whether an animation.kill() kills the scrolltrigger or do I need to kill the animation & then the scrolltrigger?
 

  useEffect(() => {
    gsap.registerPlugin(ScrollTrigger)
    const animation = gsap.fromTo(
      ref.current,
      {
        opacity: 0
      },
      {
        scrollTrigger: {
          trigger: ref.current,
          start: "top center",
          end: "bottom center",
          scrub: true,
          toggleActions: "restart none none none"
        },
        opacity: 1,
        ease: "power3.inOut"
      }
    )
    return () => {
      animation.kill() // <--- does this kill the scrolltrigger?
      animation.scrollTrigger.kill() // <--- or do I need to kill here too?
    }
  }, [])

 

Link to comment
Share on other sites

If the scrollTrigger is created inside the animation (just like you've done) - Killing the timeline will kill the associated scrollTrigger too.

  • Like 1
Link to comment
Share on other sites

8 hours ago, Cassie said:

If the scrollTrigger is created inside the animation (just like you've done) - Killing the timeline will kill the associated scrollTrigger too.


Thanks. Yeah thought so, just wasn't sure why it was documented to kill the ScrollTriggers specifically. I understand that they create fresh triggers on a re-render but I thought it might just be better to kill the whole thing!

Link to comment
Share on other sites



Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×