Animated page transitions with React and GSAP

Jonathan Erlich
Level Up Coding
Published in
6 min readJan 23, 2020

--

Photo by Chris Ried on Unsplash

What you’ll be able to do

CodeSandbox

Dependencies

To be able to make some transitions like those shown above, you’ll only need to install two additional dependencies:

  1. react-router-dom: Which will be used for routing between the different pages of the website. In our example, this dependency is responsible for routing between our Home and About pages.
  2. gsap: Which is one of the best and most robust tools out there for animating websites. In our case, we’ll explore just a tiny part of it, which is what will help us animate between page transitions.
To install these two dependencies execute the following code in your command line:yarn add react-router-dom gsapornpm install react-router-dom gsap

Transitions

First: The WHAT

The first step when creating transitions is deciding what are the elements of the current page you want to animate when entering or exiting the page. In our example, we animate all the components of the page, with the exception of the navigation menu. However, you can decide to include or exclude any element you want from your transitions.

This step is important because while React creates and renders components in a virtual DOM, GSAP needs to work with components that exist in the real DOM. To solve this issue we need to use refs, which provide a way to access DOM nodes from React.

Let’s examine how we do this for our homepage, using as an example a single component - the header that contains the Welcome message:

<div ref={div => (this.header = div)} className="home-header">  <p>Welcome!</p></div>

Specifically, the snippet ref={div => (this.header = div)} is the piece of code that will allow us to access this DOM node (this will be necessary when we animate this node with GSAP). What we are doing here is basically linking this div (which originally lives in the virtual DOM) to an “actual” DOM node that GSAP can locate. We can access this DOM node calling this.header, which is how we named the node.

We do this for 3 components within the homepage of our example. These were named this.header, this.content and this.footer. See if you can locate them in the following code and understand what’s going on:

<div ref={div => (this.header = div)} className="home-header">  <p>Welcome!</p></div><div ref={div => (this.content = div)} className="home-content">  <p>  Lorem Ipsum is simply dummy text of the printing and typesettingindustry. Lorem Ipsum has been the industry's standard dummy textever since the 1500s, when an unknown printer took a galley oftype and scrambled it to make a type specimen book. It hassurvived not only five centuries, but also the leap intoelectronic typesetting, remaining essentially unchanged. It waspopularised in the 1960s with the release of Letraset sheetscontaining Lorem Ipsum passages, and more recently with desktoppublishing software like Aldus PageMaker including versions ofLorem Ipsum.  </p></div><div ref={div => (this.footer = div)} className="home-footer">  <p>January, 2020</p></div>

We do the same for the About page. Please check the CodeSandbox to further explore how the refs were applied for that page.

Then: The HOW

The next step in creating transitions is deciding how you want to animate your different components. This step is all about choosing your effects and fine tuning them. For example, you could decide you want some element to bounce for one second while another appears gradually on the screen. Well, that’s what this step is all about — choosing and fine tuning your effects. Here’s where GSAP becomes extremely handy!

Let’s explore how we do this for our homepage.

The first thing you need to do is create a GSAP TimelineMax, which, as its name indicates, is an animation timeline that lets you decide when and how you want your animations to happen. In our homepage we create the timeline in the following piece of code, which needs to be within the constructor method of your class:

this.timeline = new TimelineMax({ paused: true });

That’s it, you’ve created your timeline! Now, we need to use it. Here, we’ll combine the refs created during the previous step with our newly minted timeline. We do this using the following code:

componentDidMount() {  this.timeline    .from(this.header, 0.5, {      display: "none",      autoAlpha: 0,      delay: 0.25,      ease: Power1.easeIn    })    .from(this.content, 0.4, {      autoAlpha: 0,      y: 25,      ease: Power1.easeInOut    })    .from(this.footer, 0.4, {      autoAlpha: 0,      y: 25,      ease: Power1.easeInOut    });  this.timeline.play();}

So, what are we doing here?

First, we are grabbing our timeline and telling it that the first component that we want to show on the screen is the one referenced by this.header, that we want to display nothing before the animation starts happening display: "none", that we want there to be a 0.25s delay before the animation begins and we want the effect to be an easing (specifically the Power1 easeIn) ease: Power1.easeIn and last but not least, that we want the animation to last for 0.5s (which is the number after this.header).

Next, we chain the following component which we want to show: this.content. The timeline will only start this animation when the previous one is finished (this can be customized however you want, but in our example, it works this way). autoAlpha: 0 gives you an animation that includes an opacity transition from 0 to 1 and lastly y: 25 gives you the position from where the animation starts happening.

Lastly, we need to literally play the timeline so everything starts to happen: this.timeline.play(). If we miss this last step, nothing will happen.

I highly encourage you to play with these variables and see how the animation behaves. Furthermore, I encourage you to explore the amazing easing effects library that GSAP offers.

Last step: The WHEN

The last thing we need to consider before creating our transitions is when we want them to happen. In our example, we want them to happen when we enter a page (to animate how the different components are displayed within the page) and whenever we exit a page (to animate how the components are withdrawn from the screen). In this example, the only way to exit a page is by clicking on the link to the other page.

The entering case is fairly easy. Since React already has a method for this called ComponentDidMount. That’s why we create and play our timeline inside the ComponentDidMount method. Whenever this page is mounting, our timeline will play. Easy.

The exiting case, however, is not so simple since React doesn’t offer a clean way to handle it. So we basically hack our way around it.

First is the consideration of when the exiting transition should happen. As we said before, for our example, it happens whenever we navigate to a different page than the one we are currently at. Then, if we’re currently at Home, the exiting transition should happen whenever we click the About link (in our case a button) in our navigation menu. In code, this looks like this for our homepage:

<header className="navigation">
<button className="nav-item"> <p>Home</p> </button>
<button
className="nav-item" onClick={e => this.changePage(e, "/about")} > <p>About</p> </button>
</header>

This header component represents our navigation menu, which has two button components: Home and About. Since this is the code for our homepage, whenever we click the Home button, nothing should happen. However, whenever we click the About button two things should happen: 1) the exit transition should kick in and 2) after the exit transition is done, it should route us to the About page. This two things happen in the changePage function, that is called whenever the About button (link) is clicked onClick={e => this.changePage(e, "/about")}. The following is the code for the changePage function:

changePage = (e, destination) => {  e.preventDefault();  this.timeline.reverse();  const timelineDuration = this.timeline.duration() * 1000;  setTimeout(() => {    this.props.history.push(destination);  }, timelineDuration);};

As said before, two things are happening here:

  1. The exit animation is kicked off. In our case we decided to just reverse the timeline: this.timeline.reverse(), but you could easily craft your own exit animations using a different timeline.
  2. After the exit animation is done, which is captured by the constant timelineDuration, we use the react-router-dom history object to jump from the current page to the about page: this.props.history.push(destination) — in this case, destination is “/about”, which corresponds to the route of our About page.

Routing won’t be explored here since it falls out of the scope of this article. However, feel free to leave any questions regarding that part in the comments section below.

Thank you for reading! I hope this was useful!

--

--