In this article I’ll show you how to animate your page transitions using lifecycle methods from ReactTransitionGroup and the Animated library.
Here’s a demo of some simple transitions using this pattern (you can also check out the live version at http://animate.mhaagens.me);
View it live at http://animate.mhaagens.me
All right, that’s it for the quick demo! Let’s see how we can set up some simple route animations!
React setup
Let’s install React with the fantastic Create React App, a simple way to get a React project up and running.
If you haven’t installed Create React App already (if you have, skip this step);
npm install -g create-react-app
Then let’s create our project;
create-react-app animatedroutes && cd animatedroutes
Then let’s install our packages for routes and animation;
yarn add react-router-dom animated react-transition-group
Now open the project in your favourite editor and run;
npm start
Adding React Router
Open your src/index.js
file and add BrowserRouter from React
import React from "react";import ReactDOM from "react-dom";import { BrowserRouter } from "react-router-dom";import App from "./App";import registerServiceWorker from "./registerServiceWorker";import "./index.css";
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>,document.getElementById("root"));
registerServiceWorker();
Then let’s create two components we can render;
First src/Home.js
;
import React, { Component } from "react";
export default class Home extends Component {render() {return (<div className="page"><h1>Home</h1><p>Hello from the home page!</p></div>)}}
Then src/Subpage.js
;
import React, { Component } from "react";
export default class Subpage extends Component {render() {return (<div className="page"><h1>Subpage</h1><p>Hello from a sub page!</p></div>)}}
Then open src/App.js
and change it to this;
import React, { Component } from 'react';import { Route, Link } from "react-router-dom";
import Home from "./Home";import Subpage from "./Subpage";
class App extends Component {render() {return (<div className="App"><div className="TopBar"><Link to="/">Home</Link><Link to="/subpage">Subpage</Link></div><Route exact path="/" component={Home} /><Route exact path="/subpage" component={Subpage} /></div>);}}
export default App;
Then remove everything in src/App.css
and paste the following in src/index.css
;
html,body,#root {height: 100%;width: 100%;}
body {margin: 0;padding: 0;font-family: sans-serif;}
.App {position: relative;display: flex;flex-flow: column;}
.TopBar {position: fixed;top: 0;left: 0;display: flex;flex-flow: row nowrap;align-items: center;width: 100%;height: 62px;padding: 0 24px;}
.TopBar a {margin-right: 18px;text-decoration: none;}
.animated-page-wrapper {position: absolute;top: 62px;left: 0;width: 100%;height: 100%;}
.page {padding: 0 24px;}
Okay. You should now be able to navigate between two routes, the homepage and a subpage.
Adding TransitionGroup
Now we’re ready to start animating the routes.There’s a few things we need to change and add to make this work;
Instead of rendering our routes the normal way, were now going to use the Route render-method to render our component and wrap them in a <TransitionGroup />
.
First import TransitionGroup in your src/App.js
component like this;
import TransitionGroup from "react-transition-group/TransitionGroup";
Then we have to add a special function for Transition Group to render a single child. Above class App extends ...
in src/App.js
, add this function;
const firstChild = props => {const childrenArray = React.Children.toArray(props.children);return childrenArray[0] || null;};
Then remove your routes and replace them with this;
<Routeexactpath="/"children={({ match, ...rest }) => (<TransitionGroup component={firstChild}>{match && <Home {...rest} />}</TransitionGroup>)}/><Routepath="/subpage"children={({ match, ...rest }) => (<TransitionGroup component={firstChild}>{match && <Subpage {...rest} />}</TransitionGroup>)}/>
You now have access to new lifecycle methods such as componentWillAppear()
, componentWillEnter()
and componentWillLeave()
.
Let’s use them to make a Higher Order Component which animates our routes! Now the real fun begins!
Creating our Animated Wrapper and animating with Animated (can I say animated again..?)
Create src/AnimatedWrapper.js
and paste in this;
import React, { Component } from "react";import * as Animated from "animated/lib/targets/react-dom";
const AnimatedWrapper = WrappedComponent => class AnimatedWrapperextends Component {constructor(props) {super(props);this.state = {animate: new Animated.Value(0)};}render() {return (<Animated.div className="animated-page-wrapper"><WrappedComponent {...this.props} /></Animated.div>);}};
export default AnimatedWrapper;
There’s a lot going on here, so I’ll explain a bit.
Were making a component to wrap our route component. It will receive the lifecycle methods from TransitionGroup, which we can use for animation.We also use Animated to create a value we can use to animate different style properties of the div that wraps our child component.
Let’s add some lifecycle methods to animate our component, and an Animated.template``
to render and/or interpolate our state animation value.
Change src/AnimatedWrapper.js
to this;
import React, { Component } from "react";import * as Animated from "animated/lib/targets/react-dom";
const AnimatedWrapper = WrappedComponent => class AnimatedWrapperextends Component {constructor(props) {super(props);this.state = {animate: new Animated.Value(0)};}componentWillAppear(cb) {Animated.spring(this.state.animate, { toValue: 1 }).start();cb();}componentWillEnter(cb) {setTimeout(() => Animated.spring(this.state.animate, { toValue: 1 }).start(),250);cb();}componentWillLeave(cb) {Animated.spring(this.state.animate, { toValue: 0 }).start();setTimeout(() => cb(), 175);}render() {const style = {opacity: Animated.template`${this.state.animate}`,transform: Animated.template`translate3d(0,${this.state.animate.interpolate({inputRange: [0, 1],outputRange: ["12px", "0px"]})},0)`};return (<Animated.div style={style} className="animated-page-wrapper"><WrappedComponent {...this.props} /></Animated.div>);}};export default AnimatedWrapper;
Then we have to import it in each of our route components and wrap them like this;
Change src/Home.js
to this;
import React, { Component } from "react";import AnimatedWrapper from "./AnimatedWrapper";
class HomeComponent extends Component {render() {return (<div className="page"><h1>Home</h1><p>Hello from the home page!</p></div>)}}
const Home = AnimatedWrapper(HomeComponent);export default Home;
and src/Subpage.js
to this;
import React, { Component } from "react";import AnimatedWrapper from "./AnimatedWrapper";
class SubpageComponent extends Component {render() {return (<div className="page"><h1>Subpage</h1><p>Hello from a sub page!</p></div>)}}
const Subpage = AnimatedWrapper(SubpageComponent);export default Subpage;
That’s it! Your routes should now be animating in and out!
Further learning
I recommend reading through the Animated docs (they’re pretty sparse at the moment, the Animated.template``
function we’re using isn’t even documented outside of Github-issues.You can look at the docs here; http://animatedjs.github.io/interactive-docs/
You can also download the example project living at http://animate.mhaagens.me/here;https://github.com/mhaagens/animated_routes_react
Follow me here on Medium or on Twitter for more React tutorials; https://twitter.com/mhaagens