Route Transitions using motion (previously framer-motion)

Route Transitions using motion (previously framer-motion)

Because animations are cool

Hey there! I am working on my portfolio and plan to use motion’s AnimatePresence and layout animations. Its very exciting to see the creative peeps coming up with these amazing transitions using motion. I have managed to animate the change of routes. So here’s a quick blog for the future me to reference to, in case I forget how to do it again.

  1. Setup

I am assuming you already know how to use react-router to set up navigation in your react app. Here’s what we are working with - a simple layout and two pages: Home and Contact.

It is a vite project created using JS + swc template. I am using MUI for styling. Since the blog is related to route animation, I am only including snippets that are relevant-

// src/routes.js
const router = createBrowserRouter([
  {
    path: '/',
    Component: RootLayout,
    children: [
      {
        path: '/',
        Component: Home
      },
      {
        path: '/connect',
        Component: Contact
      }
    ]
  }
])

// src/layouts/RootLayout.jsx
const RootLayout = () => {
  return (
    <LayoutContainer>
      <AppBar color="default" position="sticky" elevation={0}>
        <Toolbar>
          <Typography>Ravindra Nag</Typography>
        </Toolbar>
      </AppBar>
      <Outlet />
    </LayoutContainer>
  )
}

// src/App.jsx
const App = () => {
  return <RouterProvider router={router} />
}
  1. Using motion

Now that the routing has been setup, let’s install motion

npm i motion

In the layout component, we will make the changes as follows:

const RootLayout = () => {
  const outlet = useOutlet()
  const location = useLocation()

  return (
    <LayoutContainer>
      <AppBar color="default" position="sticky" elevation={0}>
        <Toolbar>
          <Typography>Ravindra Nag</Typography>
        </Toolbar>
      </AppBar>
      <AnimatePresence mode="wait">
        {outlet && cloneElement(outlet, { key: location.pathname })}
      </AnimatePresence>
    </LayoutContainer>
  )
}

AnimatePresence watches its direct children and allows them to perform exit animations. You can have any conditionally rendered child and define exit animation for it to work.

In our case, we have the outlet - which is the route element rendered by react-router. It is not conditionally rendered, so we provide a key to the outlet with current location’s path as value. So when the route changes, AnimatePresence knows that the old route element is about to exit.

We have passed the prop- mode=”wait”. It means wait for the old element to exit before allowing the new element to enter.

💡
Read about why we are not using <Outlet /> in this article: here
  1. A reusable wrapper for the pages

Next we are going to create a reusable motion wrapper for our pages because I want the same exit and enter animations for all my pages.

// src/ui/reusables/AnimatePage.jsx

const pageVariants = {
  initial: {
    y: -50,
    opacity: 0
  },
  exit: {
    opacity: 0,
    x: -100,
    transition: {
      ease: 'linear',
      duration: 0.3
    }
  },
  enter: {
    y: 0,
    opacity: 1,
    transition: {
      ease: 'linear'
    }
  }
}

const AnimatePage = ({ children, ...props }) => {
  return (
    <motion.div
      variants={pageVariants}
      initial="initial"
      exit="exit"
      animate="enter"
      {...props}
    >
      {children}
    </motion.div>
  )
}

// example (src/routes/Home.jsx)
const Home = () => {
  return (
    <AnimatePage>
      <Container>
        <Stack gap={2}>
          <Typography variant="h1">Hey there!</Typography>
          <NavLink to='/connect'>Connect</NavLink>
        </Stack>
      </Container>
    </AnimatePage>
  )
}

So now we have a reusable motion component that wraps our route components. And we are done!

Thank you for reading! 🫰🏼