Page transitions in Gatsby

Table of contents

You can find many examples on how to implement page transitions in Gatsby, but some are now outdated, some use plugins or libraries that overcomplicate things, and none of them implements transitions with some “complex” logic. Let me explain what I mean with the “complex” logic by describing what kind of page transitions I want to create.

I want to slide-up the current screen when I go from the root route / to the posts route /posts/ and slide it down when I go the opposite way. In other words, imagine that the URLs with more paths are positioned lower than those with fewer paths:

Vertical transitions example

Also, when you go through a collection of some posts, for example, I want the screen to slide left or right depending on the order of the posts.

Horizontal transitions example

In any other case, the screen will fade-in. This is just an idea I had that I wanted to implement; I don’t claim that it results in a good user experience, nor I’m suggesting that you should use it in your projects.

Transition Logic

I used a wrapPageElement layout to manage the transitions because it has the following advantages over a regular layout component:

  • It doesn’t umount when you navigate on a different page. This is useful because I want it to remember the previous pathname.
  • It has access to the location prop.

I have a function called getAnimation that takes as arguments the current pathname and the previous and returns the correct animation. It also takes an optional MDX array that has the slugs of the posts and their order. The layout calls that method and passes the result in a wrapper component, that you’ll see in the next section.

Using only reveal animations

In the first solution, that’s located in the master branch, I don’t use both reveal and exit animations. I use only reveal animations because I want something minimal. I create a wrapper component that wraps the section I want to animate, and I pass to it an emotion keyframe object—with the animation prop. I then use that animation object inside the component’s CSS. The component animates/renders when the current animation is different from the previous or when the pathname changes—I pass the pathname string to the key prop. This is not exactly the effect I had in mind, but it’s quite pleasing, especially the slide-left/right. I am a fan of those animations because they are not complicated, they are performant, if they only use CSS, and they are not so intrusive. By the way, I do something similar in this blog to fade-in the content area of the pages.

Full page

After that, I wrapped a bigger part of the layout with the wrapper component to animate the whole page, not only the content portion. The result is closer to what I had in mind, but it’s not as smooth as in the previous solution. You can find this solution in the full-page branch.

Using the transition hook from react-spring

But if I wanted to achieve the result you saw in the GIFs, I would have to use both enter and leave animations. The main problem here is that when you animate a page in, you don’t know what the exit animation will be. You also want a library that will create two pages when in fact there is only one. react-spring kind of does all of these with the useTransition hook. There are some bugs though that I didn’t investigate much.

To animate with react-spring, I did the following:

  • I changed the animations from emotion keyframes to react-spring transition objects. I didn’t change the logic inside the getAnimation function, only some imports.
  • I removed the wrapper component, and instead, I wrapped the content I wanted to animate with an animated.div. That animated.div is under a transition map in the JSX.

You can find this solution in the react-spring-transition branch.

Other things to read