How to animate page transition in React using styled components

While building the next version of RoutePlanGo, I needed a way to animate the page transition when the user navigates to different URLs in the app. RoutePlanGo is built using React and Styled Components and uses React Router for URL handling. This article explains how to use the React Transition Group to implement the page transition animation in the app.

What are we building?

Here's the example of page transition animation that we are building. This example app has Home page and Details page. When the user goes to details page, it slides in the page and updates the URL. And, when the user goes back, it slides out the details page and slides in the home page for a smooth UI experience.

Page Transition

You can see the code and the example live in here: https://codesandbox.io/s/lrxmv7304l/

1. Setting up the application

For this example, we need to have a react application. You can either use "create-react-app" in your local, or use Code Sandbox in the cloud to setup a simple react application.

Once you have the react app, please install the following modules:

# to handle CSS styles
npm install styled-components --save
# to handle URL routing
npm install react-router-dom --save
# to apply animation on component transition
npm install react-transition-group --save

Our demo app has these components: App, starting point and handles route transition between pages, HomePage and DetailsPage to demonstrate the individual pages.

App component

This is the starting point of our app and contains the page transition logic. Since, this component sits at the top level and hosts the individual pages, it makes sense to keep the page transition logic in here.

// Styled component to host the app.
const PageContainer = styled.div`
  position: relative;
  width: 100vw;
  height: 100vh;
  background-color: #e3f2fd;
  font-family: "Open Sans", sans-serif;
`;

// Starting point of our app
function App() {
  return (
    <Router>
      <Route
        render={({ location }) => {
          return (
            <PageContainer>
              <TransitionGroup component={null}>
                <CSSTransition
                  timeout={300}
                  classNames="page"
                  key={location.key}
                >
                  <Switch location={location}>
                    <Route exact path="/details" component={DetailsPage} />
                    <Route exact path="/" component={HomePage} />
                  </Switch>
                </CSSTransition>
              </TransitionGroup>
            </PageContainer>
          );
        }}
      />
    </Router>
  );
}

The App component is a pretty standard React component that uses React Router to navigate to different components based on the matching URL. But the important section in the TransitionGroup and CSSTransition. Note, how we wrap the individual routes within these two higher order components.

The TransitionGroup and CSSTransition tracks the underlying components life cycles (mounting and un-mounting) and applies the given class name (in this case, page), suffixing the current transition state name. We can use this class name to define our transition animations.

For example, when the HomePage is mounted, it will have the class name page-enter added to it. Similarly, when it is getting unmounted (for example, when the user navigates to DetailsPage), it will have page-exit class name applied and the DetailsPage will have page-enter added to its class list.

HomePage component

HomePage contains some text and a link to navigate to DetailsPage.

// animation to slide in the home page from left
const slideInLeft = keyframes`
  from {
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
    visibility: visible;
  }

  to {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
`;

// animation to slide out the home page to the left
const slideOutLeft = keyframes`
  from {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }

  to {
    visibility: hidden;
    -webkit-transform: translate3d(-100%, 0, 0);
    transform: translate3d(-100%, 0, 0);
  }
`;

const Page = styled.div``;

// apply the correct animation based on the transition state class name.
const HomePageElm = styled(Page)`
  &.page-enter {
    animation: ${slideInLeft} 0.2s forwards;
  }
  &.page-exit {
    animation: ${slideOutLeft} 0.2s forwards;
  }
`;

function HomePage() {
  return (
    <HomePageElm>
      <h2>Home Page</h2>
      <Link to="/details">Go to Page Two</Link>
    </HomePageElm>
  );
}

Again, the HomePage component is pretty standard React component. But look at how we define the animations and apply the animation on the component level based the transition state class names. Especially,

const HomePageElm = styled(Page)`
  &.page-enter {
    animation: ${slideInLeft} 0.2s forwards;
  }
  &.page-exit {
    animation: ${slideOutLeft} 0.2s forwards;
  }
`;

Also, make sure that your animation timings is less than or equal to the timeout prop of CSSTransition component. If it doesn't match, your animation may look janky. Play around with these timings to have a smooth animation.

The same logic applies to the DetailsPage component as well. We just need to replace the correct animations. You can view this example and the complete code in live here: https://codesandbox.io/s/lrxmv7304l