Animating React Components With GreenSock
During the early days of the World Wide Web, things were rather static and boring. Webpages were mostly based on graphic design and layouts from the print world until animations were introduced. Animation can engage and hold people’s attention longer than a static web page and communicates an idea or concept more clearly and effectively.
However, when not done right, animations can hamper user interactions with your product and negatively impact traction. The GreenSock Animation Platform AKA (GSAP) is a powerful JavaScript library that enables front-end developers, animators and designers to create performant timeline based animations. It allows animation lovers take precise control of their animation sequences rather than the sometimes constraining keyframe
and animation
properties that CSS offers.
In this article, I’ll introduce you to some features of GSAP such as scrollTriggers
, Timelines
, Easing
etc, at the end we’ll build an intuitive user interface by animating a React app with this features👌. Check out the finished project on codesandbox.
This article will be useful to you if:
- You have been building animations on web applications with HTML, CSS, and JavaScript.
- You are already building animated webpages in a React apps with packages like animate.css, React-motion, Framer-motion, and React-Spring, plus you want to check out alternatives.
- You are a React enthusiast, and you’d like to build complex animations on React-based web applications.
We will look at how to build a variety of animations from an existing web project. Let’s get to it!
Note: This article assumes you are comfortable with HTML, CSS, JavaScript, and React.js.
What Is GSAP?
GreenSock Animation Platform also known as GSAP is an Ultra high-performance, professional-grade animation for the modern web that allows developers to animate their apps in a modular, declarative, and re-usable fashion. It is framework-agnostic and can be used across any JavaScript based project, it has a very minimal bundle size and will not bloat your app.
GSAP can perform canvas animations, used to create WebGL experiences, and create dynamic SVG animations and as great browser support.
Why Use GSAP?
Maybe you’re not quite ready to betray other frameworks yet, or you haven’t been convinced to embrace the goodies that come with GSAP. Allow me to give you a few reason why you may want to consider GSAP.
You Can Build Complex Animations
GSAP JavaScript library makes it possible for developers to build simple to very complex physics-based animations like in the case of these sites, it allows developers and designers sequence motion and controls the animation dynamically. It has lots of plugins such as DrawSVGPlugin, MorphSVGPlugin, and more, which makes creating SVG based animations and 2D/3D animations a reality. Asides integrating GSAP on DOM elements, you can use them within WebGL/Canvas/ Three.js context-based animations.
Furthermore, the easing capacity of GSAP is quite sophisticated, hence making it possible to create advance effects with multiple beziers as compared to the regular CSS animation.
Performance
GSAP has an impressive high performance across different browsers.
According to GSAP’s team, in their website, “GSAP is 20x faster than jQuery, plus GSAP is the fastest full-featured scripted animation tool on the planet. It's even faster than CSS3 animations and transitions in many cases.” Confirm speed comparison for yourself.
Furthermore, the GSAP animations perform effortlessly on both desktop computers, tablets, and smartphones. It is not needed to add a long list of prefixes, this is all being taken care of under the hood by GSAP.
You can check out more benefits on GSAP or see what Sarah Drasner as to say about it here.
Cons Of GSAP
Are you saying I should always use GSAP for every project? Of course not! I feel like, there’s only one reason you might not want to use GSAP. Let’s find out!
- GSAP is solely a JavaScript-based animation library, hence it requires some knowledge of JavaScript and DOM manipulation to effectively utilize its methods and APIs. This learning curve downside leaves even more room for complications for a beginner starting out with JavaScript.
- GSAP doesn’t cater to CSS based animations, hence if you are looking for a library for such, you might as well use
keyframes
in CSS animation.
If you’ve got any other reason, feel free to share it in the comment section.
Alright, now that your doubts are cleared, let’s jump over to some nitty-gritty in GSAP.
GSAP Basics
Before we create our animation using React, let’s get familiar with some methods and building blocks of GSAP.
If you already know the fundamentals of GSAP, you can skip this section and jump straight to the project section, where we’ll make a landing page skew while scrolling.
Tween
A tween is a single movement in an animation. In GSAP, a tween has the following syntax:
TweenMax.method(element, duration, vars)
Let’s take a look at what this syntax represents;
method
refers to the GSAP method you’ll like to tween with.element
is the element you want to animate. If you want to create tweens for multiple elements at the same time, you can pass in an array of elements toelement
.duration
is the duration of your tween. It is an integer in seconds (without thes
suffix!).vars
is an object of the properties you want to animate. More on this later.
GSAP methods
GSAP provides numerous methods to create animations. In this article, we’d mention only a few such as gsap.to
, gsap.from
, gsap.fromTo
. You can check out other cool methods in their documentation. The methods discussed in this section will be used in building our project later in this tutorial.
gsap.to()
the values to which an object should be animated i.e the end property values of an animated object — as shown below:gsap.to('.ball', {x:250, duration: 5})
To demonstrate the to
method the codepen demo below shows that an element with a class of ball 250px
will move across the x-axis
in five seconds when the components mounts. If a duration isn't given, a default of 500 milliseconds would be used.
See the Pen GSAP REACT DEMO1 by Blessing Krofegha.
Note: x
and y-axis
represent the horizontal and vertical axis respectively, also in CSS transform properties such as translateX
and translateY
they are represented as x
and y
for pixel-measured
transforms and xPercent
and yPercent
for percentage-based transforms.
To view the complete snippet of the code check the codepen playground.
gsap.from()
— Defines the values an object should be animated from — i.e., the start values of an animation:gsap.from('.square', {duration:3, scale: 4})
The codepen demo show how an element with a class of square
is resized from a scale of 4 in 3seconds
when the components mounts. Check for the complete code snippet on this codepen.
See the Pen GSAP REACT DEMO2 by Blessing Krofegha.
gsap.fromTo()
— lets you define the starting and ending values for an animation. It is a combination of both thefrom()
andto()
method.
Here’s how it looks;
gsap.fromTo('.ball',{opacity:0 }, {opacity: 1 , x: 200 , duration: 3 });
gsap.fromTo('.square', {opacity:0, x:200}, { opacity:1, x: 1 , duration: 3 });
This code would animates the element with a class of ball
from an opacity of 0 to an opacity of 1
across the x-axis
in 3 seconds
and the square
class is animated the from an opacity of 0
to 1
in 3 seconds
across the x-axis
only when the component mounts. To see how the fromTo
method works and the complete code snippet, check the demo on CodePen below.
See the Pen React GSAP FromTo demo by Blessing Krofegha.
Note: Whenever we’re animating positional properties, such as left
and top
, we must ensure that the elements concerned must have a CSS position property of either relative
, absolute
, or fixed
.
Easing
GSAP official documentation defined easing as the primary way to change the timing of your Tweens. It determines how an object changes position at different points. Ease controls the rate of change of animation in GSAP and is used to set the style of an object’s animation.
GSAP provides different types of eases and options to give you more control over how your animation should behave. It also provides an Ease Visualizer to help you choose your preferred ease settings.
There are three types of eases, and they vary in their operations.
in()
— Motion starts slowly, then picks up the pace toward the end of the animation.out()
— The animation starts fast then slows down at the end of the animation.inOut()
— The animation begins slow, picks up the pace halfway through, and ends slowly.
See the Pen React GSAP Easing demo by Blessing Krofegha.
In these easing example, we chained the tweens that displayed the three types of eases bounce.in
, bounce.out
and bounce.inOut
, and set a delay of the number of seconds it takes the animation to complete before starting the next one only when the component is mounts. This pattern is repetitive, in the next next section we would see how we could use a timeline to do this better.
Timelines
A Timeline acts as a container for multiple tweens. It animates tweens in sequential order, and it is not dependent on the duration of the previous tween. Timeline makes it simple to control tweens as a whole and precisely manage their timing.
Timelines can be written by creating an instance of a timeline like so:
gsap.timeline();
You can also chain multiple tweens to a timeline in two different ways, in the code below:
##Method 1
const tl = gsap.timeline(); // create an instance and assign it a variable
tl.add(); // add tween to timeline
tl.to('element', {});
tl.from('element', {});
##Method 2
gsap.timeline()
.add() // add tween to timeline
.to('element', {})
.from('element', {})
Let’s recreate the previous example with a timeline:
const { useRef, useEffect } = React;
const Balls = () => {
useEffect(() => {
const tl = gsap.timeline();
tl.to('#ball1', {x:1000, ease:"bounce.in", duration: 3})
tl.to('#ball2', {x:1000, ease:"bounce.out", duration: 3, delay:3 })
tl.to('#ball3', {x:1000, ease:"bounce.inOut", duration: 3, delay:6 })
}, []);
}
ReactDOM.render(, document.getElementById('app'));
Inside a useEffect
hook, we created a variable(tl)
that holds an instance of a timeline, next we used the tl
variable to animate our tween in sequential without depending on the previous tween to animate, passing the same properties as it were in the previous example. For the complete code snippet of this demo check the codepen playground below.
See the Pen React GSAP (Easing with Timeline) demo by Blessing Krofegha.
Now that we have gotten a feel of some the basic building blocks of GSAP, let’s see how we could build a complete animation in a typical React app in the next section. Let’s begin the flight! 🚀
Building an Animated Landing Page with React and GSAP
Let’s get to animate a React App. Ensure you clone the repo before you begin and run npm install
, to install the dependencies.
What are we building?
Currently, our landing page contains a few texts a white background, a menu that doesn’t drop down, with really no animation. The following are what we’ll be adding to the landing page;
- Animate the text and the logo on the homepage, so it eases out when the component is mounted.
- Animate the menu, so it drops down when the menu is clicked.
- Make the images in the gallery page skew
20deg
when the page scrolls.
Check out the demo on codesandbox.
We’ll break the process of our landing page into components, so it will be easy to grasp. Here’s the process;
- Define the animation methods,
- Animate text and logo,
- Toggle menu,
- Make images skew
20deg
on page scroll.
components
Animate.js
— Defined all animation methods,Image.js
— import galley images,Menu.js
— Contains the menu toggle functionality,Header.js
— Contains navigation links.
Define animation methods
Create a component
folder inside the src
directory, and create an animate.js
file. Copy and paste the following code into it.
import gsap from "gsap"
import { ScrollTrigger } from "gsap/ScrollTrigger";
//Animate text
export const textIntro = elem => {
gsap.from(elem, {
xPercent: -20,
opacity: 0,
stagger: 0.2,
duration: 2,
scale: -1,
ease: "back",
});
};
Here, we imported gsap
. We wrote an exported arrow function that animates the text on the landing page. Remember that gsap.from()
method defines the values an object should be animated from. The function has an elem
parameter that represents the class which needs to be animated. It takes a few properties and assigns values such as xPercent: -20
(transforms the object by -20%), gives the object no opacity, makes the object scale
by -1
, makes the object ease
back in 2sec
.
To see if this works, head over to App.js
and include the following code.
...
//import textIntro
import {textIntro} from "./components/Animate"
...
//using useRef hook to access the textIntro DOM
let intro = useRef(null)
useEffect(() => {
textIntro(intro)
}, [])
function Home() {
return (
<div className='container'>
<div className='wrapper'>
<h5 className="intro" ref={(el) => (intro = el)}></h5>
The <b>SHOPPER</b>, is a worldclass, innovative, global online ecommerce platform,
that meets your everyday daily needs.
</h5>
</div>
</div>
);
}
Here, we import the textIntro
method from the Aminate
component. To access the DOM we used to useRef
Hook. We created a variable intro
whose value is set to null
. Next, inside the useEffect
hook, we called the textIntro
method and the intro
variable. Inside our home component, in the h5
tag, we defined the ref
prop and passed in the intro
variable.
Next, we have got a menu, but it isn’t dropping down when it’s clicked. Let’s make it work! Inside the Header.js
Component, add the code below.
import React, { useState, useEffect, useRef } from "react";
import { withRouter, Link, useHistory } from "react-router-dom";
import Menu from "./Menu";
const Header = () => {
const history = useHistory()
let logo = useRef(null);
//State of our Menu
const [state, setState] = useState({
initial: false,
clicked: null,
menuName: "Menu",
});
// State of our button
const [disabled, setDisabled] = useState(false);
//When the component mounts
useEffect(() => {
textIntro(logo);
//Listening for page changes.
history.listen(() => {
setState({ clicked: false, menuName: "Menu" });
});
}, [history]);
//toggle menu
const toggleMenu = () => {
disableMenu();
if (state.initial === false) {
setState({
initial: null,
clicked: true,
menuName: "Close",
});
} else if (state.clicked === true) {
setState({
clicked: !state.clicked,
menuName: "Menu",
});
} else if (state.clicked === false) {
setState({
clicked: !state.clicked,
menuName: "Close",
});
}
};
// check if out button is disabled
const disableMenu = () => {
setDisabled(!disabled);
setTimeout(() => {
setDisabled(false);
}, 1200);
};
return (
<header>
<div className="container">
<div className="wrapper">
<div className="inner-header">
<div className="logo" ref={(el) => (logo = el)}>
<Link to="/">SHOPPER.</Link>
</div>
<div className="menu">
<button disabled={disabled} onClick={toggleMenu}>
{state.menuName}
</button>
</div>
</div>
</div>
</div>
<Menu state={state} />
</header>
);
};
export default withRouter(Header);
In this component, we defined our menu and button state, inside the useEffect
hook, we listened for page changes using useHistory
hook, if the page changes we set the clicked
and menuName
state values to false
and Menu
respectively.
To handle our menu, we checked if the value of our initial state is false, if true, we change the value of initial
, clicked
and menuName
to null
, true
and Close
. Else we check if the button is clicked, if true we’d change the menuName
to Menu
. Next, we have a disabledMenu
function that disables our button for 1sec
when it’s clicked.
Lastly, in our button
, we assigned disabled
to disabled
which is a boolean value that will disable the button when its value is true
. And the onClick
handler of the button is tied to the toggleMenu
function. All we did here was toggle our menu
text and passed the state to a Menu
component, which we would create soonest. Let’s write the methods that will make our menu dropdown before creating the actual Menu
component. Head over to Animate.js
and paste this code into it.
....
//Open menu
export const menuShow = (elem1, elem2) => {
gsap.from([elem1, elem2], {
duration: 0.7,
height: 0,
transformOrigin: "right top",
skewY: 2,
ease: "power4.inOut",
stagger: {
amount: 0.2,
},
});
};
//Close menu
export const menuHide = (elem1, elem2) => {
gsap.to([elem1, elem2], {
duration: 0.8,
height: 0,
ease: "power4.inOut",
stagger: {
amount: 0.07,
},
});
};
Here, we have a function called menuShow
, which skews the menu horizontally by 2degrees
, eases the menu, offset’s the animation using the stagger
property, and transforms the menu from right to top
in 0.7sec
, the same properties go for the menuHide
function. To use these functions, create Menu.js
file inside the components
and paste this code into it.
import React, {useEffect, useRef} from 'react'
import { gsap } from "gsap"
import { Link } from "react-router-dom"
import {
menuShow,
menuHide,
textIntro,
} from './Animate'
const Menu = ({ state }) => {
//create refs for our DOM elements
let menuWrapper = useRef(null)
let show1 = useRef(null)
let show2 = useRef(null)
let info = useRef(null)
useEffect(() => {
// If the menu is open and we click the menu button to close it.
if (state.clicked === false) {
// If menu is closed and we want to open it.
menuHide(show2, show1);
// Set menu to display none
gsap.to(menuWrapper, { duration: 1, css: { display: "none" } });
} else if (
state.clicked === true ||
(state.clicked === true && state.initial === null)
) {
// Set menu to display block
gsap.to(menuWrapper, { duration: 0, css: { display: "block" } });
//Allow menu to have height of 100%
gsap.to([show1, show2], {
duration: 0,
opacity: 1,
height: "100%"
});
menuShow(show1, show2);
textIntro(info);
}
}, [state])
return (
<div ref={(el) => (menuWrapper = el)} className="hamburger-menu">
<div
ref={(el) => (show1 = el)}
className="menu-secondary-background-color"
></div>
<div ref={(el) => (show2 = el)} className="menu-layer">
<div className="container">
<div className="wrapper">
<div className="menu-links">
<nav>
<ul>
<li>
<Link
ref={(el) => (line1 = el)}
to="/about-us"
>
About
</Link>
</li>
<li>
<Link
ref={(el) => (line2 = el)}
to="/gallery"
>
Gallery
</Link>
</li>
<li>
<Link
ref={(el) => (line3 = el)}
to="/contact-us"
>
Contact us
</Link>
</li>
</ul>
</nav>
<div ref={(el) => (info = el)} className="info">
<h3>Our Vision</h3>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit....
</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Menu
What we did in the Menu
component was to import the animated functions, which are menuShow
, menuHide
, and textIntro
. Next, we assigned variables for each created refs
for our DOM
elements using the useRef
hook and passed null
as their values. Inside the useEffect
hook, we check for the state of the menu
, if clicked
is false
, we call the menuHide
function, otherwise, if the clicked
state is true we call the menuShow
function. Lastly, we ensured that the DOM
elements concerned are passed their specific refs
which are menuWrapper
, show1
, show2
. With that, we’ve got our menu animated.
Let’s see how it looks.
Animated Menu.The last animation we would implement is make our images in our gallery skew
when it scrolls. Let’s see the state of our gallery now.
To implement the skew animation on our gallery, let’s head over to Animate.js
and add a few codes to it.
....
//Skew gallery Images
export const skewGallery = elem1 => {
//register ScrollTrigger
gsap.registerPlugin(ScrollTrigger);
// make the right edge "stick" to the scroll bar. force3D: true improves performance
gsap.set(elem1, { transformOrigin: "right center", force3D: true });
let clamp = gsap.utils.clamp(-20, 20) // don't let the skew go beyond 20 degrees.
ScrollTrigger.create({
trigger: elem1,
onUpdate: (self) => {
const velocity = clamp(Math.round(self.getVelocity() / 300));
gsap.to(elem1, {
skew: 0,
skewY: velocity,
ease: "power3",
duration: 0.8,
});
},
});
}
We created a function called skewGallery
, passed elem1
as a param, and registered ScrollTrigger
.
ScrollTrigger is a plugin in GSAP that enables us to trigger scroll-based animations, like in this case of skewing the images while the page scrolls.
To make the right edge stick to the scroll bar we passed right center
value to the transformOrigin
property, we also set the force3D
property to true in other to improve the performance.
We declared a clamp
variable that calculates our skew and ensures it doesn’t exceed 20degs
. Inside the ScrollTrigger
object, we assigned the trigger
property to the elem1
param, which would be the element that needs to be triggered when we call this function. We have an onUpdate
callback function, inside it is a velocity
variable that calculates the current velocity and divides it by 300
.
Lastly, we animate the element from their current values by setting other values. We set skew
to initially be at 0
and skewY
to be the velocity
variable at 0.8
.
Next, we’ve got to call this function in our App.js
file.
....
import { skewGallery } from "./components/Animate"
function Gallery() {
let skewImage = useRef(null);
useEffect(() => {
skewGallery(skewImage)
}, []);
return (
<div ref={(el) => (skewImage = el)}>
<Image/>
</div>
)
}
....
Here, we imported skewGalley
from ./components/Animate
, created a skewImage
ref that targets the image element. Inside the useEffect
hook, we called the skewGallery
function and passed the skewImage
ref as a param. Lastly, we passed the skewImage
to the ref
to attribute.
You’d agree with me it was such a pretty cool journey thus far. Here’s the preview on CodeSanbox 👇
The supporting repo for this article is available on Github.
Conclusion
We’ve explored the potency of GSAP in a React project, we only scratched the surface in this article, there’s no limit to what you can do with GSAP as it concerns animation. GSAP’s official website offers additional tips to help you gain a thorough understanding of methods and plugins. There’s a lot of demos that would blow your mind away with what people have done with GSAP. I’d love to hear your experience with GSAP in the comment section.
Resources
- GSAP Documentation — GSAP
- The Beginner’s Guide to the GreenSock Animation Platform — Freecodecamp
- An introduction to animations with Greensock Animation API (GSAP) — Zellwk