How to use the GSAP ScrollTrigger plugin in React

How to use the GSAP ScrollTrigger plugin in React

Scrollytelling can be complicated to implement on a website without the use of scroll driven animation libraries. These libraries provide a simple interface for creating different interactions on the scroll and improving user experience.

Over the years, there has been a significant improvement in scroll driven animation libraries that power scrollytelling in JavaScript. From the launch of SuperScrollorama by John Polacek in 2013 to ScrollMagic by Jan Paepke in 2014, and the recent announcement of ScrollTrigger by GSAP in 2020.

ScrollTrigger is the reinvention of scroll-driven animation but in a more suitable and user-friendly way, giving you the ability to control the flow of GSAP animations or any other thing while scrolling. In this tutorial, you will learn about the GSAP ScrollTrigger plugin, how to use it to trigger animations on the scroll in React, and the use cases for it.

Prerequisites

This tutorial assumes the reader has the following:

  1. Node >= 8.10 installed on their local development machine
  2. npx 5.2 or higher installed on their local development machine
  3. Basic understanding of how to animate elements with GSAP
  4. A basic knowledge of HTML, CSS, JavaScript, and React

Introducing the GSAP ScrollTrigger plugin

GSAP is an acronym for the GreenSock Animation Platform. It is arguably the best animation library for the web because it can animate DOM elements, canvas, SVG, CSS, WebGL, generic JavaScript objects, and so much more.

The creators of GSAP strongly believe it is the fastest full-featured scripted animation tool on the planet.

ScrollTrigger is built on the GSAP and can be used to trigger those intriguing GSAP animations on the scroll with just a few lines of code, excellent performance, cross-browser compatibility, and support from the GSAP community.

Use case for ScrollTrigger

In this section we’ll look at the importance of ScrollTrigger, and when you should use it.

There are three circles in the demo below. The third circle has been animated with GSAP to move along the x-axis of the page for two seconds. The third circle might not be visible to you at first glance, so you need to scroll down.

On scrolling down, you'll notice that the third circle had already moved towards the x-axis before you reached that section of the page. Yeah, I know. That's not cool.

The good news is, ScrollTrigger solves this problem with its ability to let you trigger the animations when a user reaches a specified viewport while scrolling.

The possibilities of the GSAP ScrollTrigger plugin

Here are some of the things you can do with ScrollTrigger:

  • Animate anything (DOM, CSS, SVG, WebGL, and Canvas) on the scroll
  • Toggle playback state or scrub through animations
  • Automatic resizing on different screens
  • Support for vertical and horizontal scrolling
  • Ability to pin elements in place

Find out more about these possibilities on ScrollTrigger's website.

ScrollTrigger basics

Before we trigger our animations on the scroll using ScrollTrigger, let's get familiar with the basics.

This tutorial assumes that the reader has a fair understanding of how to animate using GSAP, so we will only cover the basics of ScrollTrigger. Find a fantastic resource to get you up to speed with GSAP here.

The ScrollTrigger basics you will learn in this section will be used in building our project later in this tutorial. You can check out the full list of ScrollTrigger properties and methods in their documentation.

If you already know the fundamentals of ScrollTrigger, you can skip this section and jump straight to the project section, where we'll build a simple landing page in React and trigger the animations on the scroll with ScrollTrigger.

trigger

The trigger property is used to specify the point we want our animation to start. To add a trigger property, use the syntax below:

trigger: "element"

With this property, we can make the third circle in our previous demo the trigger point for the animation. This means that the circle will only move towards the x-axis when we reach the specified element’s (the third circle, in this case) viewport.

Let's set the #thirdCircle as the trigger point and see if anything changes:

trigger: "#thirdCircle"

Moment of truth! 😱

Now, you can see that the #thirdCircle starts animating as soon as it comes into view. Exciting right?

markers

markers is a development-enabling feature that allows you to see the start and end position of the element to be animated and the page's viewport.

Let's add markers to our previous demo using the syntax below:

markers: true

If we scroll down to the trigger element (the third circle), we will notice that the start and end position is now visible thanks to markers.

start

By default, the trigger element starts animating as soon as it enters the bottom of the scroller's viewport.

However, we can change the default start position using the ScrollTrigger start property. Let's change the start position of the animation in our previous demo using the code below:

start: "top center"

  • Here, top refers to the top of the trigger element, which is thirdCircle in this case
  • center signifies the center of the webpage

Aside from using keywords like top, bottom, and center to control the animation's position, we can also use pixel or percentage values.

end

Just like we could change the start position, we can also change the end position using ScrollTrigger's end property.

By default, the trigger element stops animating as soon the bottom enters the top of the scroller's viewport. Let's change the end position of the animation in our demo using the code below.

end: "bottom top"

  • Here bottom signifies the bottom of the trigger element thirdCircle
  • top here signifies the top of the webpage

scrub

The scrub property links the scroll position to an animation's progress. When you set scrub to true, it makes the scroll bar act as a scrubber while it's between the start and end position property.

Let's add a scrub using the syntax scrub:true to our demo to get a clearer understanding:

If you scroll through the page and look closely, you will notice that the trigger element (thirdCircle) moves along the x-axis when you scroll down, but if you scroll up again, it takes it back to the original position, thereby acting like a scrubber.


So far, we've learned that ScrollTrigger allows you to trigger an animation when the page is scrolled to a specific viewport. We've also covered the basics of ScrollTrigger, now let's move on to using ScrollTrigger in a React app.

Using ScrollTrigger in a React app

Yay! The time we've all been waiting for is here. In this section, we will build a basic landing page with React, animate the landing page, and trigger the animations using ScrollTrigger.

Check out the demo on Codesandbox and code repository on GitHub.

This section has been broken down into different steps to make it easier to understand.

Step 1: Create a React project

First, let's spin up a React application by using Create React App:

npx create-react-app scrolly

The command above will create a React application called scrolly for us. Once the application has been created successfully, switch to its repository using cd scrolly and run the command below:

npm start

If the React app has been created correctly, you should see this in your browser window when you navigate to localhost:3000.

React Homepage

Step 2: Add content to our React app

Open the newly created React app in your favorite integrated development environment (IDE) and paste the code below inside the App.js file:

import "./App.css";
import workout from "./workout.svg";
import greensocklogo from "./greensocklogo.svg";
import happy from "./happy.svg";

function App() {
  return (
    <div className="App">
      <div className="first">
        <h1>ScrollTrigger</h1>
        <p className="first-paragraph">
          is the coolest Greensock plugin.
          <span role="img" aria-label="celebrating">
            🥳
          </span>
        </p>
        <div className="logo-main">
          <img src={workout} id="workout-logo" alt="workout" />
        </div>
      </div>
      <div className="second">
        <div className="logo-main">
          <img src={greensocklogo} id="gsap-logo" alt="greensocklogo" />
        </div>
      </div>
      <div className="third">
        <p>
          <span className="line" />
        </p>
        <div className="logo-main">
          <img src={happy} id="happy-logo" alt="happy" />
        </div>
      </div>
    </div>
  );
}
export default App;

The code above is a basic functional React component that we will use to animate our landing page.

Now, let me explain the code bit by bit:

import './App.css';
import workout from "./workout.svg";
import greensocklogo from "./greensocklogo.svg";
import happy from "./happy.svg";

Here, we imported all the external files we need to build the landing page.

The SVG images imported in the code snippet above can be found here. To add these files to your React app, create a file for each of the SVG images (workout.svg, greensocklogo.svg, and happy.svg), copy the SVG code here, and paste it into the files you created:

function App() {
  return (
    <div className="App">
      <div className="first">
        <h1>ScrollTrigger</h1>
        <p className="first-paragraph">
          is the coolest Greensock plugin.
          <span role="img" aria-label="celebrating">
            🥳
          </span>
        </p>
        <div className="logo-main">
          <img src={workout} id="workout-logo" alt="workout" />
        </div>
      </div>
      <div className="second">
        <div className="logo-main">
          <img src={greensocklogo} id="gsap-logo" alt="greensocklogo" />
        </div>
      </div>
      <div className="third">
        <p>
          <span className="line" />
        </p>
        <div className="logo-main">
          <img src={happy} id="happy-logo" alt="happy" />
        </div>
      </div>
    </div>
  );
}

export default App;

Here, we have a parent div element with three child div elements. This section controls what is displayed on our landing page. Finally, we exported our App component so that we can reuse it in different sections of the React app.

If you added the SVG's images correctly, there shouldn't be any error, and the landing page should look like this now. 👇🏽

Step 3: Customize the landing page

As you can see, our landing page isn't styled adequately, so make it more appealing with CSS. To do this, paste the code below into your App.css file:

* {
    padding: 0%;
    margin: 0%;
    box-sizing: border-box;
}

.first {
    width: auto;
    height: 500px;
    background-color: #fdfffc;
    margin-top: 30px;
}

.second {
    width: auto;
    height: 400px;
    background-color: #e0fbfc;
}

.third {
    width: auto;
    height: 400px;
    background-color: #fdfffc;
}

.line {
    width: 100%;
    max-width: 1400px;
    height: 20px;
    margin-top: 20px;
    position: relative;
    display: inline-block;
    background-color: #023047;
}

h1 {
    text-align: center;
    padding-top: 90px;
    color: #023047;
    font-size: 60px;
}

.first-paragraph {
    text-align: center;
    color: #023047;
    font-size: 20px;
    font-weight: bold;
}

.logo-main {
    align-items: center;
    display: flex;
    justify-content: center;
}

#workout-logo {
    width: 500px;
    height: 300px;
    margin-top: 10px;
}

#gsap-logo {
    width: 500px;
    height: 300px;
    margin-top: 50px;
    padding-right: 80px;
}

#happy-logo {
    width: 500px;
    height: 300px;
}

.second-paragraph {
    font-size: 30px;
    margin-top: 40px;
}

.third-paragraph {
    font-size: 30px;
    margin-top: 40px;
}

With the CSS we just added, our app should look like this. 👇🏽

Step 4: Target elements using refs

We need to target the elements we intend to animate on the landing page using the useRef React hook.

Paste the updated code below into your App.js file:

import { useRef } from "react";
import "./App.css";
import workout from "./workout.svg";
import greensocklogo from "./greensocklogo.svg";
import happy from "./happy.svg";

function App() {
  const ref = useRef(null);

  return (
    <div className="App" ref={ref}>
      <div className="first">
        <h1>ScrollTrigger</h1>
        <p className="first-paragraph">
          is the coolest Greensock plugin.
          <span role="img" aria-label="celebrating">
            🥳
          </span>
        </p>
        <div className="logo-main">
          <img src={workout} id="workout-logo" alt="workout" />
        </div>
      </div>

      <div className="second">
        <div className="logo-main">
          <img src={greensocklogo} id="gsap-logo" alt="greensocklogo" />
        </div>
      </div>

      <div className="third">
        <p>
          <span className="line" />
        </p>
        <div className="logo-main">
          <img src={happy} id="happy-logo" alt="happy" />
        </div>
      </div>
    </div>
  );
}

export default App;

Here’s an explanation of the code changes:

import {useRef} from "react";

We imported the useRef() React Hook into App.js:

const ref = useRef(null);

Then we created a variable called ref that will be used to store the reference of our target element (the parent div element in this case).

<div ref={ref} className="App">
    ...
</div>

Finally, we passed the mutable ref object into the parent div element.

Step 5: Animate the React ref using GSAP

Now that we have targeted the element we intend to animate, let’s animate it with GSAP. First, install GSAP into your React app by running the command below in your terminal:

npm install gsap

After installing GSAP into the React app successfully, paste the updated code below into your App.js file:

import { useRef, useEffect } from "react";
import "./App.css";
import workout from "./workout.svg";
import greensocklogo from "./greensocklogo.svg";
import happy from "./happy.svg";
import { gsap } from "gsap";

function App() {
  const ref = useRef(null);
  useEffect(() => {
    const element = ref.current;
    gsap.fromTo(
      element.querySelector(".first-paragraph"),
      {
        opacity: 0,
        y: -20,
      },
      {
        opacity: 1,
        y: 0,
      }
    );
  }, []);

  useEffect(() => {
    const element = ref.current;
    gsap.fromTo(
      element.querySelector("#gsap-logo"),
      {
        opacity: 0,
        scale: 0.2,
        y: -20,
      },
      {
        opacity: 1,
        y: 0,
        scale: 1,
        duration: 1,
        ease: "none",
      }
    );
  }, []);

  useEffect(() => {
    const element = ref.current;
    gsap.from(element.querySelector(".line"), {
      scale: 0,
      ease: "none",
    });
  }, []);

  return (
    <div className="App" ref={ref}>
      <div className="first">
        <h1>ScrollTrigger</h1>
        <p className="first-paragraph">
          is the coolest Greensock plugin.
          <span role="img" aria-label="celebrating">
            🥳
          </span>
        </p>
        <div className="logo-main">
          <img src={workout} id="workout-logo" alt="workout" />
        </div>
      </div>

      <div className="second">
        <div className="logo-main">
          <img src={greensocklogo} id="gsap-logo" alt="greensocklogo" />
        </div>
      </div>

      <div className="third">
        <p>
          <span className="line" />
        </p>
        <div className="logo-main">
          <img src={happy} id="happy-logo" alt="happy" />
        </div>
      </div>
    </div>
  );
}

export default App;

Here’s an explanation of the changes we made to the existing code in our App.js file:

import { useRef, useEffect } from "react";
import { gsap } from "gsap";

We imported the useEffect() React Hook and gsap into App.js:

useEffect(() => {
    const element = ref.current;
    ...
}, []);

We initialized three different useEffect() React Hooks for the three child div elements. Inside each useEffect() Hook, we created a constant variable called element and assigned it a reference to the parent div element we targeted using the useRef() React Hook:

useEffect(() => {
  const element = ref.current;
  gsap.fromTo(
    element.querySelector(".first-paragraph"),
    {
      opacity: 0,
      y: -20,
    },
    {
      opacity: 1,
      y: 0,
    }
  );
}, []);

useEffect(() => {
  const element = ref.current;
  gsap.fromTo(
    element.querySelector("#gsap-logo"),
    {
      opacity: 0,
      scale: 0.2,
      y: -20,
    },
    {
      opacity: 1,
      y: 0,
      scale: 1,
      duration: 1,
      ease: "none",
    }
  );
}, []);

useEffect(() => {
  const element = ref.current;
  gsap.from(element.querySelector(".line"), {
    scale: 0,
    ease: "none",
  });
}, []);

In each of the three hooks, we added GSAP tweens to animate the three child div elements; .first-paragraph, #gsap-logo, and .line.

With this new update, our landing page now looks like this. 👇🏽

If you look at the landing page now, you will notice that we've successfully animated the .first-paragraph, #gsap-logo, and .line child elements.

However, these animations start animating once the page is loaded into the browser window preventing us from seeing our animations in action. I bet you didn’t see the animation of the .line element at the end of the landing page.

Let's fix this by adding the ScrollTrigger plugin to ensure the animations are only triggered on the scroll.

Step 6: Trigger animations on the scroll using ScrollTrigger

Yay! It is time to see the ScrollTrigger plugin in action.

Paste the code below into the App().js file:

import { useRef, useEffect } from "react";
import "./App.css";
import workout from "./workout.svg";
import greensocklogo from "./greensocklogo.svg";
import happy from "./happy.svg";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

function App() {
  gsap.registerPlugin(ScrollTrigger);
  const ref = useRef(null);
  useEffect(() => {
    const element = ref.current;
    gsap.fromTo(
      element.querySelector(".first-paragraph"),
      {
        opacity: 0,
        y: -20,
      },
      {
        opacity: 1,
        y: 0,
        scrollTrigger: {
          trigger: element.querySelector(".first"),
          start: "top top",
          end: "bottom center",
          scrub: true,
        },
      }
    );
  }, []);
  useEffect(() => {
    const element = ref.current;
    gsap.fromTo(
      element.querySelector("#gsap-logo"),
      {
        opacity: 0,
        scale: 0.2,
        y: -20,
      },
      {
        opacity: 1,
        y: 0,
        scale: 1,
        duration: 1,
        ease: "none",
        scrollTrigger: {
          trigger: element.querySelector(".first"),
          start: "top center",
          end: "bottom top",
          scrub: true,
        },
      }
    );
  }, []);
  useEffect(() => {
    const element = ref.current;
    gsap.from(element.querySelector(".line"), {
      scale: 0,
      ease: "none",
      scrollTrigger: {
        trigger: element.querySelector(".third"),
        scrub: true,
        start: "top bottom",
        end: "top top",
      },
    });
  }, []);
  return (
    <div className="App" ref={ref}>
      <div className="first">
        <h1>ScrollTrigger</h1>
        <p className="first-paragraph">
          is the coolest Greensock plugin.
          <span role="img" aria-label="celebrating">
            🥳
          </span>
        </p>
        <div className="logo-main">
          <img src={workout} id="workout-logo" alt="workout" />
        </div>
      </div>
      <div className="second">
        <div className="logo-main">
          <img src={greensocklogo} id="gsap-logo" alt="greensocklogo" />
        </div>
      </div>
      <div className="third">
        <p>
          <span className="line" />
        </p>
        <div className="logo-main">
          <img src={happy} id="happy-logo" alt="happy" />
        </div>
      </div>
    </div>
  );
}
export default App;

Here’s an explanation of the changes we made to the code:

import { ScrollTrigger } from "gsap/ScrollTrigger";

We imported the ScrollTrigger plugin into App.js:

gsap.registerPlugin(ScrollTrigger);

Then we registered the ScollTrigger plugin:

useEffect(() => {
  const element = ref.current;
  gsap.fromTo(
    element.querySelector(".first-paragraph"),
    {
      opacity: 0,
      y: -20,
    },
    {
      opacity: 1,
      y: 0,
      scrollTrigger: {
        trigger: element.querySelector(".first"),
        start: "top top",
        end: "bottom center",
        scrub: true,
      },
    }
  );
}, []);

In each of our useEffect() Hooks, we added scrollTrigger as one of the values in our gsap tween.

scrollTrigger: {
    trigger: element.querySelector(".first"),
    start: "top top",
    end: "bottom center",
    scrub: true
}

We added our trigger element in this case, the .first element. Then we also specified the start and end position for the animation. Finally, we set scrub to true so that all our animations will act as a scrubber when we scroll through the landing page.

With the updated changes, our landing page's animations will only start animating when we reach the specified viewport while scrolling through the web page. See demo below. 👇🏽


Conclusion

I hope you were able to learn about the GSAP ScrollTrigger plugin in this tutorial. Even though this tutorial covers the fundamental possibilities of using ScrollTrigger, there's so much more you can do with ScrollTrigger. You might be inspired by the demos on GreenSock's CodePen.

If you have any questions, you can leave them in the comments section below.

Resources for further reading