Creating Page Transitions in React using Framer Motion

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Page transitions are animated transition between pages which are used to give websites that extra touch that distinguishes them as a top-notch and worthy of a good browse, when applied correctly they not only give a sense of liveliness but also helps greatly with navigation.

We all love to see smooth and cool page transitions when browsing websites. I've always been fascinated by them and wanted to learn how to add those to my sites. Libraries like Barba makes it very easy to add them in our site, but they don't work well with React.

Thankfully, Framer Motion makes it possible (and easier) to create sleek and smooth page transitions in React and similar libraries and in this article, I'll show you how. Scroll down to the bottom if you want to see the final demo.

Framer Motion

The basics of Framer Motion involve creating a motion component and passing some values to set the styles we want an element to start at and animate to. So if I want to have an element to come in from the left when it's rendered, I can play with the x property. Check out the sandbox below, try changing x to y and see what happens.

<motion.div
    initial={{ opacity: 0, x: "-100vh" }}
    animate={{ opacity: 1, x: 0 }}
>
   <p>Animated text</p>
 </motion.div>

AnimatePresence

In the above example, you should notice that the top text slides in as it's being mounted, while the normal text renders without any sort of transition. The slide in is smoother, but both texts exhibit the same behavior when they are unmounted. Even though our motion.div component slides in, it doesn't slide out.

To get the desired behavior, we can import and wrap AnimatePresence around our conditionally rendered element. Now we can use the exit prop on our motion.div to have the element slide out as it's being unmounted.

<AnimatePresence>
          {isDisplayed && (
            <>
              <motion.div
                initial={{ x: "-100vh" }}
                animate={{ x: 0 }}
                exit={{ x: "100vh" }}
              >
                <p>Animated text</p>
              </motion.div>
              <p>Normal text</p>
            </>
          )}
</AnimatePresence>

Pretty cool, huh? Let's reuse the above logic to apply animation when a route is changed in this simple react application.

Starter Sandbox

Our first step is to wrap our pages inside a <AnimatePresence>. Where we wrap it will depend on where our router is rendering the pages. In our case, we'll do that in App.js file because that's where routes are defined.

AnimatePresence demands that each of its direct children needs to have a unique key prop so it can track their presence in the tree. We can use the location object from the useLocation hook by importing it from react-router-dom.

Here's how the App.js file will look like after making the necessary changes. Notice that I've kept the <Navbar/> component outside of AnimatePresence because we don't want it to animate with the whole page.

import React from "react";
import { Route, Switch, useLocation } from "react-router-dom";
import { AnimatePresence } from "framer-motion";

import Home from "./pages/Home.js";
import About from "./pages/About.js";
import Contact from "./pages/Contact.js";
import Navbar from "./components/Navbar.js";

const App = () => {
  const location = useLocation();

  return (
    <>
      <Navbar />
      <AnimatePresence exitBeforeEnter>
        <Switch location={location} key={location.pathname}>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Switch>
      </AnimatePresence>
    </>
  );
};

export default App;

If you look closely you will notice that I've added a prop called exitBeforeEnter to AnimatePresence. This prop guarantees that our component will have unmounted (i.e. completed its exit animation) before allowing the new component to animate in. If we remove this prop the exit and enter animation will take place at the same time and we won't be able to see the enter animation while changing routes.

Now all that's left is to use a motion component in our pages and define our initial, animate, and exit props like we did earlier!

So, we'll wrap the return value of all the three pages of our site with a motion.div component and add the necessary props. We'll also add a transition prop to change the duration of the animation so that we can see it more easily. Here's how Home.js will look like after making the changes.

import React from "react";
import Banner from "../components/Banner.js";
import { motion } from "framer-motion";

const Home = () => {
  return (
    <motion.div
      initial={{ opacity: 0, x: -200 }}
      animate={{ opacity: 1, x: 0 }}
      exit={{ opacity: 0, x: 200 }}
      transition={{ duration: 0.7 }}
    >
      <Banner title="Welcome stranger!" subtitle="Put something witty here!" />
      <div className="container">
        <h2>Welcome</h2>
        <p>
          Lorem ipsum........
        </p>
        <p>
          Maecenas dapibus.......
        </p>
      </div>
    </motion.div>
  );
};
export default Home;

Final Demo

Conclusion

I hope you enjoyed reading the article and learned something from it.

We have only scratched the surface of what framer motion can do. In the next post, we'll use some advanced features of framer motion like AnimateSharedLayout to create a fairly complex page transition.

No Comments Yet