Layout Animations with Framer Motion

Jan 30, 2021

How to use Framer Motion's AnimateSharedLayout component in a Gatsby site

This post assumes a basic knowledge of React and Gatsby. Some knowledge of Framer Motion is also helpful, but not necessary.

This past week, my team and I at Apollo GraphQL launched our brand new learning platform, Odyssey! To give it some extra UI polish, we used the animation library Framer Motion.

In my last post, I went into the AnimatePresence component and how we can use it to enable both enter and exit animations as a component is being mounted and unmounted. This is the methodology we used for almost all of the page transitions in Odyssey! We navigating between lessons, the content slides either left or right, depending on whether you're moving forwards or backwards in the course. If you're going back to the homepage, we used a fade transition.

The transition animation from the homepage to course details page, however, is noticeably different! On the homepage, we have a list of cards with some info about each of the courses. Clicking on a card navigates to a new page that contains more detailed information about that course. On the route change, the card expands to the details page or shrinks back down to the homepage.

Odyssey page transition from homepage to a course details page

To accomplish this, we used the AnimateSharedLayout component from Framer Motion. Let's dive into how we can use it to accomplish this card effect!

AnimateSharedLayout

The AnimateSharedLayout component allows us to perform layout animations between components that don't otherwise share state OR between components that have the same layoutId prop as they're added or removed from the React tree.

In our case with the card animation on route changes, we're doing a layout animation between components as the smaller card is being unmounted and the course details page is being mounted. As long as those components share a layoutId, Framer Motion will take care of animating them for us!

Structure

To enable these kinds of layout animations, we need to wrap our components with the AnimateSharedLayout component. Since we're using it to handle page transitions, we need to make sure that AnimateSharedLayout wraps all of our pages. In a typical React single page application, we can simply wrap it around our app. For example, if we're using Reach Router to handle our routes, we can import AnimateSharedLayout from Framer Motion and wrap our components. That way when we navigate to a new page, AnimateSharedLayout is never unmounted.

router-exampleJS
1import React from 'react';
2import {AnimateSharedLayout} from 'framer-motion';
3import { Router } from "@reach/router";
4
5import Home from "./components/Home";
6import About from "./components/About";
7
8const App = () => (
9 <AnimateSharedLayout>
10 <Router>
11 <Home path="/" />
12 <About path="/about" />
13 </Router>
14 </AnimateSharedLayout>
15)
16
17export default App;

Gatsby approach

The approach for using this component with Gatsby is very similar to how we approached using the AnimatePresence component in the last post. Since Gatsby creates pages at build time, we need to make sure that AnimateSharedLayout isn't being unmounted during route changes. To accomplish this, we can utilize the wrapPageElement API in our gatsby-browser.js file in the root of our project.

gatsby-browser.jsJS
1import React from 'react';
2import {AnimateSharedLayout} from 'framer-motion';
3
4export const wrapPageElement = ({element}) => (
5 <AnimateSharedLayout>{element}</AnimateSharedLayout>
6);

Now all of our pages will be wrapped in a persistent AnimateSharedLayout component that won't be unmounted as we navigate between different pages.

Layout animation

Now that we have our app set up with AnimateSharedLayout, it's time to animate! To recreate the card expansion animation, we'll need to create a Card component.

Card.jsJS
1import React from "react"
2import { Link } from "gatsby"
3import { motion } from "framer-motion"
4
5const Card = ({ item }) => {
6 return (
7 <motion.li
8 layoutId={`item-${item}`}
9 style={{
10 width: "80px",
11 height: "50px",
12 borderRadius: "5px",
13 backgroundColor: "darkseagreen",
14 listStyle: "none",
15 textAlign: "center"
16 }}
17 >
18 <Link to={`/page-${item}/`}>
19 <p>Item #{item}</p>
20 </Link>
21 </motion.li>
22 )
23}

In our Card component, we imported motion from Framer Motion and made our entire card a motion component. Then, we passed it a layoutId. When we click on the card to navigate to its linking page, if there is a motion component on that page with a matching layoutId, Framer Motion will animate the transition between the two.

Now we need to make some pages for our cards to route and animate to. You can set up your pages to look however you want. In order to see a layout animation, make sure to import motion from Framer Motion, create a motion component, and give it a layoutId that matches the layoutId of its card from the HomePage.

Demo

Here's a basic example putting it all together:

In an effort to make the code simpler, I decided to forego adding more styling and animations to fade in and out the page content that lives outside of the card to make the whole page transition a bit smoother. Feel free to experiment with both AnimateSharedLayout and AnimatePresence to make some beautiful page transitions.

Summary

Pretty cool, huh? Let's recap what we had to do:

  • Wrap our pages in wrapPageElement with AnimateSharedLayout
  • Create some sort of card component on one page that is a motion component with a layoutId that links to another page in our app
  • Create a separate page for our card to link to that has a motion component with a layoutId that matches its card

Framer Motion gave us that nice animation right out of the box! No extra configuration was required for the animation. You can go deeper with your layout animations and combine AnimateSharedLayout with AnimatePresence to make some really neat effects. As always, be sure to check out the docs! If you make something cool with Framer Motion, let me know on Twitter! I'd love to see what you're making ๐Ÿ˜„