Animating page transitions in Gatsby

Animating page transitions in Gatsby

Motivation πŸ”₯

I’m totally enjoying Gatsby for various reasons and in this post I want to share how easy it is to add customized page transitions to your website to make it more lively and smooth.

We will be using Gatsby default starter to make this example as isolated as possible but you can be sure that it will also work for more complex starters and projects created by you from the scratch.

As a teaser, we will build something similar to what you see when you follow links on this siteπŸ˜ƒ

Getting started πŸ› οΈ

If you are new to Gatsby and want to follow along this tutorial be sure to install Gatsby command line interface(Gatsby CLI) so you can quickly bootstrap new projects in the future.

npm install --global gatsby-cli

Navigate to your project folder and create new Gatsby project inside of it by running the following command in the terminal:

gatsby new .

It will create a project with simplest possible setup and it should look like this: (it may vary for the further iterations on starter’s design)

gatsby starter

Installing necessary dependencies βš™οΈ

First of all we need to install react-transition-groupwhich is responsible for watching out for elements entering and leaving the DOM and applying animations to them.

npm install react-transition-group

We will also install gatsby-plugin-layout that in our case serves the purpose of providing location property required for transitions to work and injects our layout to every page.

npm install gatsby-plugin-layout

To make plugin work as expected we need to move our layout component into layouts folder at the root of our project and rename it to index.js. Let’s also add transition.js to our components folder where we will supply all the transition logic. For now leave it empty as we have a little more to configureπŸ˜„

    β”œβ”€β”€ components
    β”‚Β Β  β”œβ”€β”€ header.js
    β”‚Β Β  β”œβ”€β”€ image.js
    β”‚Β Β  └── transition.js
    β”œβ”€β”€ images
    β”‚Β Β  β”œβ”€β”€ gatsby-astronaut.png
    β”‚Β Β  └── gatsby-icon.png
    β”œβ”€β”€ layouts
    β”‚Β Β  β”œβ”€β”€ index.js
    β”‚Β Β  └── layout.css
    └── pages
        β”œβ”€β”€ 404.js
        β”œβ”€β”€ index.js
        └── page-2.js

Last step is adding our gatsby-plugin-layout to gatsby-config.js file that is located in the root of our project

plugins: [
    'gatsby-plugin-react-helmet',
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    'gatsby-plugin-layout',
    'gatsby-transformer-sharp',
    'gatsby-plugin-sharp',

Transition component 🎩

It holds the full logic for transition that will be applied when user decides to follow a link to other page on our site.

Inside transition.js add the following code which I will explain in comments:

//Import necessary dependencies 

import React from "react"
import {
    TransitionGroup,
    Transition as ReactTransition,
} from "react-transition-group"

//This variable will be responsible for our animation duration
const timeout = 500

//This object contains basic styles for animation, but you can extend them to whatever you like. Be creative!
const getTransitionStyles = {
    entering: {
        position: 'absolute',
        opacity: 0,
    },
    entered: {
        transition: `opacity ${timeout}ms ease-in-out`,
        opacity: 1,
    },
    exiting: {
        transition: `all ${timeout}ms ease-in-out`,
        opacity: 0
    },
}

class Transition extends React.PureComponent {
    render() {
        //Destructuring props to avoid garbage this.props... in return statement
        const { children, location } = this.props

        return (
            //Using TransitionGroup and ReactTransition which are both 
            //coming from  'react-transition-group' and are required for transitions to work
            <TransitionGroup>
                <ReactTransition
                //the key is necessary here because our ReactTransition needs to know when pages are entering/exiting the DOM
                    key={location.pathname}
                    //duration of transition
                    timeout={{
                        enter: timeout,
                        exit: timeout,
                    }}
                >
                    {
                        //Application of the styles depending on the status of page(entering, exiting, entered) in the DOM
                        status => (
                        <div
                            style={{
                                ...getTransitionStyles[status],
                            }}
                        >
                            {children}
                        </div>
                    )}
                </ReactTransition>
            </TransitionGroup>
        )
    }
}

export default Transition

Now we can import Transition component into Layout component and wrap children which represent our pages inside of it.

//Note that we need to pass location to our functional component  so we have access to it down there in <Transition/>
const Layout = ({ children, location }) => (
  <StaticQuery
    query={graphql`
      query SiteTitleQuery {
        site {
          siteMetadata {
            title
          }
        }
      }
    `}
    render={data => (
      <>
        <Helmet
          title={data.site.siteMetadata.title}
          meta={[
            { name: 'description', content: 'Sample' },
            { name: 'keywords', content: 'sample, something' },
          ]}
        >
          <html lang="en" />
        </Helmet>
        <Header siteTitle={data.site.siteMetadata.title} />
        
        <Transition location={location}>
          {children}
        </Transition>
      </>
    )}
  />
)

Layout.propTypes = {
  children: PropTypes.node.isRequired,
}

export default Layout

At this point you may experience a bug when some of your page elements being rendered twice. To solve that just go trough files in pages folder and make sure that they don’t import <Layout> component and use in return statement.

Now that everything is working as expected and you are enjoying your newly added page transitions you may notice a slight jerky bug that appears when your page is being scrolled down and animation starts. Note that it happens when there is more content on the page and you can scroll.

We can easily fix this with the help of including this code in our gatsby-browser.js which you can find in the root of our project. What we do here is actually setting time out for animation and waiting for it to be executed until page scrolls to the top.

const transitionDelay = 500

exports.shouldUpdateScroll = ({
    routerProps: { location },
    getSavedScrollPosition,
}) => {
    if (location.action === 'PUSH') {
        window.setTimeout(() => window.scrollTo(0, 0), transitionDelay)
    } else {
        const savedPosition = getSavedScrollPosition(location)
        window.setTimeout(
            () => window.scrollTo(...(savedPosition || [0, 0])),
            transitionDelay
        )
    }
    return false
    }

I hope you’ve enjoyed this small post and will use your new knowledge whenever you need it. Here you can find a link to GitHub repo with the working code for this tutorial.


Comments section

Dimitri Ivashchuk

Written by Dimitri who lives and works in Vienna building useful things. He is passionate about modern web technologies and enjoys learning something new everyday as well as sharing his knowledge with others.

Front-end engineer @CubeTech
Instructor @egghead.io