• Web development
  • Simple page transition in NextJS with GSAP and react-transition-group

    Sébastien Graf

    Sébastien Graf

    06 August 2022

    Simple page transition in NextJS with GSAP and react-transition-group

    We're going to learn one way of animating page transitions in NextJS with two libraries: GSAP and react-transition-group.

    Prerequisites

    • Basic knowledge of NextJS and its page routing
    • Basic knowledge of React
    • Know how to install a module with NPM

    Creating the project

    Create our NextJS app with create-next-app. We will name it page-transition.

    npx create-next-app page-transition
    

    Now you can cd (enter) inside our newly created and we can proceed to install GSAP, our animation library, and react-transition-group to listen to page transitions.

    cd page-transition
    
    npm i gsap react-transition-group
    

    I will be using tailwind to speed up the process of styling this small project, but feel free to use CSS to style it as you please.

    You can follow tailwind official guide to install it in your NextJS project in case you want to use it as well.

    Start the server.

    npm run dev
    

    When you go to http://localhost:3000/ you should now see your NextJS starter project.

    Creating a layout

    We will now create a layout that we can reuse on our different pages. Create a new folder at the root of your directory called components, and inside it, we can create a new file called Header.jsx and another one calledLayout.jsx.

    In Header.jsx, we create a simple header that will have a title on the left and links to our pages on the right:

    // components/Header.jsx
    
    import Link from 'next/link'
    
    const Header = () => {
      return (
        <header className='max-w-5xl flex justify-between mx-auto mb-8 px-4 py-8 border-b border-black/20'>
          <h1 className='text-2xl font-semibold'>Page Transition</h1>
    
          <nav className='flex gap-4 text-lg text-blue-700'>
            <Link href='/'>Home</Link>
            <Link href='/page1'>Page 1</Link>
            <Link href='/page2'>Page 2</Link>
          </nav>
        </header>
      )
    }
    
    export default Header
    

    We are using next/link for our internal routing. If you want to add another HTML element inside the Link component, be sure to add a property called passHref to the Link.

    Now let's import the header inside our Layout component, and then actually create the layout that we can re-use :

    // components/Layout.jsx
    
    import Header from './Header'
    
    const Layout = ({ children }) => {
      return (
        <>
          <Header />
          <main className='p-4 mx-auto max-w-4xl'>{children}</main>
        </>
      )
    }
    
    export default Layout
    

    We are passing children as a prop, which will be everything that is nested inside our page layout.

    Using our layout

    Now that we have our base layout created, we can wrap our whole app inside so that every page can share the header and our main style.

    In _app.js, import our Layout and wrap it around our pageProps

    // pages/_app.js
    
    import Layout from '../components/layout'
    import '../styles/globals.css'
    
    function MyApp({ Component, pageProps }) {
      return (
        <Layout>
          <Component {...pageProps} />
        </Layout>
      )
    }
    
    export default MyApp
    

    Creating our pages

    We already have a default home page called index.js, but we want more! So we are going to create 2 additional pages in the same folder. Let's call them page1.js and page2.js.

    Our styled home page looks like this.

    // pages/index.js
    
    export default function Home() {
      return (
          <section className='flex flex-col gap-8 items-center'>
            <h2 className='text-4xl font-semibold'>Home page</h2>
            <p>
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Nobis dolor
              aspernatur natus at totam quae temporibus, optio mollitia numquam est
              ab ut non et omnis quas in, laudantium nemo. Nisi, culpa. Porro ab,
              ducimus rerum, sed eum facilis dolorum a veniam cumque sit molestias
              quaerat nobis! Nulla veritatis ea eius!
            </p>
            <p>
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Ullam
              voluptatem voluptas ipsa voluptatibus nihil totam a sequi vel tempore
              incidunt. Possimus, obcaecati soluta non in nostrum eaque nemo quod
              adipisci eos architecto expedita eum quis amet dolorem voluptas quas
              impedit voluptatem aperiam eveniet, reiciendis, minima ex. Consectetur
              totam voluptas quisquam.
            </p>
          </section>
      )
    }
    

    page1.js and page2.js are the same, only the text inside our H2 changes.

    This is page1.js

    // pages/page1.js
    
    export default function Page1() {
      return (
          <section className='flex flex-col gap-8 items-center'>
            <h2 className='text-4xl font-semibold bg-red-200 px-4 py-2'>
              First Page
            </h2>
            <p>
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Nobis dolor
              aspernatur natus at totam quae temporibus, optio mollitia numquam est
              ab ut non et omnis quas in, laudantium nemo. Nisi, culpa. Porro ab,
              ducimus rerum, sed eum facilis dolorum a veniam cumque sit molestias
              quaerat nobis! Nulla veritatis ea eius!
            </p>
            <p>
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Ullam
              voluptatem voluptas ipsa voluptatibus nihil totam a sequi vel tempore
              incidunt. Possimus, obcaecati soluta non in nostrum eaque nemo quod
              adipisci eos architecto expedita eum quis amet dolorem voluptas quas
              impedit voluptatem aperiam eveniet, reiciendis, minima ex. Consectetur
              totam voluptas quisquam.
            </p>
          </section>
      )
    }
    

    And this is page2.js

    // pages/page2.js
    
    export default function Page2() {
      return (
          <section className='flex flex-col gap-8 items-center'>
            <h2 className='text-4xl font-semibold bg-yellow-200 px-4 py-2'>
              Second Page
            </h2>
            <p>
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Nobis dolor
              aspernatur natus at totam quae temporibus, optio mollitia numquam est
              ab ut non et omnis quas in, laudantium nemo. Nisi, culpa. Porro ab,
              ducimus rerum, sed eum facilis dolorum a veniam cumque sit molestias
              quaerat nobis! Nulla veritatis ea eius!
            </p>
            <p>
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Ullam
              voluptatem voluptas ipsa voluptatibus nihil totam a sequi vel tempore
              incidunt. Possimus, obcaecati soluta non in nostrum eaque nemo quod
              adipisci eos architecto expedita eum quis amet dolorem voluptas quas
              impedit voluptatem aperiam eveniet, reiciendis, minima ex. Consectetur
              totam voluptas quisquam.
            </p>
          </section>
      )
    }
    

    Your app should now have a working routing system when you click on the links inside the header.

    Screenshot 2022-08-06 at 11.51.03.png

    Creating the animations

    Now comes the fun part, we will animate the transitions using GSAP and react-transition-group.

    Head back to your Layout.jsx file and import our two libraries as well as nextjs/router useRouter.

    import { SwitchTransition, Transition } from 'react-transition-group'
    import gsap from 'gsap'
    
    import { useRouter } from 'next/router'
    

    We are now going to create two methods inside our Layout component. These functions will be the animations when our page enters and when it exits. We are using gsap.fromTo() method, which takes in our element as the first parameter, and then two objects with our initial state and our end state.

    We are animating the y property (which interpolates translateY) and autoAlpha, which is similar to opacity but sets the visibility to hidden when the opacity reaches 0. The ease is set to power3 (which is a GSAP-specific easing)

    const onPageEnter = (node) => {
      gsap.fromTo(
        node,
        {
          y: 50,
          autoAlpha: 0,
          ease: 'power3',
        },
        {
          y: 0,
          autoAlpha: 1,
          duration: 1,
          ease: 'power3',
        }
      )
    }
    
    
    const onPageExit = (node) => {
      gsap.fromTo(
        node,
        {
          y: 0,
          autoAlpha: 1,
          ease: 'power3.out',
        },
        {
          y: -50,
          autoAlpha: 0,
          duration: 0.5,
          ease: 'power3.inOut',
        }
      )
    }
    

    Animate our route change

    We will now use react-transition-group SwitchTransition and Transition components to manage our page transitions.

    In our Layout component, we need to wrap our main element inside Transition, and Transitioninside SwitchTransition. We are not including the header inside it because we don't want to animate it every time our route changes.

    <SwitchTransition>
      <Transition>
        <main className='p-4 mx-auto max-w-4xl'>{children}</main>
      </Transition>
    </SwitchTransition>
    

    The Transition component will accept a few arguments. You can find them on the documentation to get more info on what they do.

    We are going to use the following:

    <Transition
      key={router.asPath} // our route as a key
      timeout={500}
      in={true}
      onEnter={onPageEnter} // our enter animation
      onExit={onPageExit} // our exit animation
      mountOnEnter={true}
      unmountOnExit={true}>
        <main className='p-4 mx-auto max-w-4xl'>{children}</main>
    </Transition>
    

    Notice how our Transition component has a router object passed inside as the key. You can just declare it at the top of our component like so:

    const router = useRouter()
    

    This is what your Layout.jsx should look like now:

    // components/Layout.jsx
    
    import { useRouter } from 'next/router'
    import { SwitchTransition, Transition } from 'react-transition-group'
    import gsap from 'gsap'
    import Header from './Header'
    
    const Layout = ({ children }) => {
      const router = useRouter()
    
      const onPageEnter = (element) => {
        gsap.fromTo(
          element,
          {
            y: 50,
            autoAlpha: 0,
            ease: 'power3.out',
          },
          {
            y: 0,
            autoAlpha: 1,
            duration: 1,
            ease: 'power3.out',
          }
        )
      }
    
      const onPageExit = (element) => {
        gsap.fromTo(
          element,
          {
            y: 0,
            autoAlpha: 1,
            ease: 'power3.out',
          },
          {
            y: -50,
            autoAlpha: 0,
            duration: 0.5,
            ease: 'power3.inOut',
          }
        )
      }
      return (
        <>
          <Header />
          <SwitchTransition>
            <Transition
              key={router.pathname}
              timeout={500}
              in={true}
              onEnter={onPageEnter}
              onExit={onPageExit}
              mountOnEnter={true}
              unmountOnExit={true}>
              <main className='p-4 mx-auto max-w-4xl'>{children}</main>
            </Transition>
          </SwitchTransition>
        </>
      )
    }
    
    export default Layout
    

    Voilà!

    Here is our final result

    We are all done, now you can enjoy a nice simple transition between your different pages where the component smoothly fades up and then comes back from the bottom. Feel free to play around with GSAP tweens until you find a transition you like!

    You can check out the GitHub repository from this tutorial here

    Sébastien Graf
    Author

    Sébastien Graf

    Hey, I'm Seb, a front-end web developer who likes everything tech related.

    Post a comment

    Related Posts