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:
- Node >= 8.10 installed on their local development machine
- npx 5.2 or higher installed on their local development machine
- Basic understanding of how to animate elements with GSAP
- 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 isthirdCircle
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 elementthirdCircle
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
.
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
- GreenSock 101, Petr Tichy
- GSAP Cheatsheet, GreenSock
- ScrollTrigger Documentation, GreenSock
- Most Common ScrollTrigger Mistakes, GreenSock