Make your web app feel native with React page transitions

Make your web app feel native

Gav McKenzie
Author
Gav McKenzie
Published
Jul 5, 2018
Topics
How-to, Industry, Resource

One of the things separating the web from native apps is a lack of page transitions. Jumping from page to page with no UI feedback can make the web feel “cheaper”.

Let’s add some class by using React Router 4 and react-transition-group to set up some awesome page transitions.

tl;dr https://codesandbox.io/s/qqxj18wj9

App setup

First, we’re going to need a simple React app wrapper with some routing. We’ll use the BrowserRouter from React Router to wrap our app. As the BrowserRouter only works if the browser supports the HTML5 history API, we do a check to see if we need to fully refresh the page. Sadly this will kill the transitions in older browsers, but the website will still work!

const supportsHistory = 'pushState' in window.history;

React Router v4 is a little more lax with route matching so we need to use a Switch component to make sure only one route is rendered at a time. We’ll catch any errors with a pathless route at the end of the Switch.

const App = () => (
<BrowserRouter forceRefresh={!supportsHistory}>
<div>
<Nav />
<main>
<Switch>
<Route
exact
path="/"
component={Home}
/>
<Route
path="/about"
component={About}
/>
<Route
component={Error404}
/>
</Switch>
</main>
</div>
</BrowserRouter>
);

Now our app is set up and we have some navigation happening, we need to make it go whizz!

Component setup

We’ll start by wrapping the area we want to transition in a Route component with no path so it always renders. In the render prop, we need to grab the location object that is passed in as props, as well as the pathname.

<Route
render={({ location }) => {
const { pathname } = location;
...

We need the location object in order to prevent the route inside the transition from re-rendering whilst we are exiting the page from the viewport.

We’ll need to use the pathname to set a unique key on the CSSTransition component so the TransitionGroup understands it needs to kick off the transition when our route changes.

There are two components we need from react-transition-group: TransitionGroup and CSSTransition.

We use TransitionGroup as a wrapper for our CSSTransition component to update the in prop correctly when our route changes.

We use CSSTransition to manage our base class name for the transition and the timeouts (how long before the transition classes are removed and the exiting DOM element).

<TransitionGroup>
<CSSTransition
key={pathname}
classNames="page"
timeout={{
enter: 1000,
exit: 1000,
}}
>
[...children]
</CSSTransition>
</TransitionGroup>

CSSTransition will add the classes page-enter and page-enter-active on enter, then page-exit and page-exit-active on exit so we can hook onto them and animate the page as we need. Page enter/exit will be applied as soon as the transition starts, whilst the active classes will be applied in order to activate the CSS animation.

It’s possible to fully customise these classes to match your preferred style of class naming.

Inside these components we add another Route component with the location prop set using the location object we stored earlier to prevent it changing during transition.

Finally, we hit our Switch and routing from the app setup.

<Route
location={location}
render={() => (
<Switch>
<Route
exact
path="/"
component={Home}
/>
<Route
path="/about"
component={About}
/>
<Route
component={Error404}
/>
</Switch>
)}
/>

We’ve now got routes that stay on the page for 1 second after the route changes and some useful styling classes to hook onto to create the page transitions!

CSS Setup

If we have 2 DOM elements on the page at once they will bump each other down so we’re going to make the Page component position: fixed. We’ll also add a transition to the transform property to we can animate it in and out.

In order to remain Jank Free, it’s best to try and stick to animating only transform and opacity where possible.

.page {
height: 100vh;
padding: calc(15% + 1.5em) 5% 5%;
overflow-y: auto;
position: fixed;
top: 0;
width: 100%;
-webkit-overflow-scrolling: touch;
transition: transform 1s ease-in-out;
}

Now we have a base layout, we can cue up the enter and exit animations.

We’ll start page-enter with the state we want the page to be in as it arrives in the DOM. We’ll then use page-enter-active to describe the state we want at the end of the animation.

Page-exit is a little simpler as the component is already in the DOM so we just need to describe where we want it to be before it leaves.

.page-enter {
transform: translate(-100%, 0);
}
.page-enter-active {
transform: translate(0, 0);
}
.page-exit {
transform: translate(-100%, 0);
}

This gives us pages that swipe left every time we navigate to a new route.

Fine tuning

Sometimes you want the animation to run slightly differently depending on where you have come from. We can use the object version of the to prop in React Router Link to pass a state to the page component.

The state property passes data to the new route without it being reflected in the URL bar, which can be super useful.

<Link
to={{
pathname: '/about',
state: { prev: true },
}}
className="nav__link"
>
About
</Link>

We’ll attach the Page component to the router using the Higher Order Component withRouter so we can listen to the passed state.

function Page({
location: {
state,
},
}) {
const cx = classNames({
page: true,
'page--prev': state && state.prev,
});
return (
<section className={cx}>
{children}
</section>
);
}
[...]
export default withRouter(Page);

We use the state and the classnames module to update the classes on the Page component and drive slightly different transitions, sending the pages back and forth rather than always in the same direction.

.page--prev.page-enter {
transform: translate(100%, 0);
}
.page--prev.page-enter-active {
transform: translate(0, 0);
}
.page--prev.page-exit {
transform: translate(100%, 0);
}

And that’s how to animate between page views with React Router v4 and react-transition-group!

Demo: https://codesandbox.io/s/olql83nlky

Repo: https://github.com/etchteam/react-page-transitions