DEV Community

Discussion on: Using Framer Motion to make page transitions in React

Collapse
 
willem_rabe profile image
Willem Rabe

Hey Sam,

thanks so much for the thoughful reply!
I didn't quite make clear what I wanted to achieve:

Say I have pages A, B and C.
A link from page A to C should have a specific exit animation on A and a specific initial animation on C (say: A moves out to the left, C moves in from the right), while a link from B to C would have a different set (B exiting to the top, C moving in from the bottom). Think of a two-dimensional grid of pages you want to move around in.

Here's my somewhat clunky solution:

I first define a number of variants for initial and exit animations:

const animationVariants = {
  visible: { 
    x: 0,
    transition: { duration: 0.4, ease: "easeInOut" }
  },
  enterLeft:{
    x:'-100vw',
    transition: { duration: 0.4, ease: 'easeInOut' }
  },
  enterRight:{
    x:'100vw',
    transition: { duration: 0.4, ease: 'easeInOut' }
  },
  exitLeft:{
    x:'-100vw',
    transition: { duration: 0.4, ease: 'easeInOut' }
  },
  exitRight:{
    x:'100vw',
    transition: { duration: 0.4, ease: 'easeInOut' }
  }
};
Enter fullscreen mode Exit fullscreen mode

I then use framer motion's useCycle hook to keep track of what exit animation is currently set in state:

const [exitAnimation, cycleExitAnimation] = useCycle("exitLeft", "exitRight")
Enter fullscreen mode Exit fullscreen mode

I pass both the exitVariants and exitAnimation to my container element:

<motion.div 
  variants={animationVariants}
  initial={initAnimation}
  animate="visible"
  exit={exitAnimation}
    >
Enter fullscreen mode Exit fullscreen mode

In my react-router Link I have an anonymous function run onClick that will cycle the exit animation if it's not the one appropriate for this very link.

<Link onClick={
        () => {
          if(exitAnimation === "exitLeft"){
            cycleExitAnimation()
          }
        }
      }  to={{
          pathname: "/about/subpage",
          state: {prevPath: location.pathname}
          }}>A subpage</Link>
Enter fullscreen mode Exit fullscreen mode

You will notice I am also passing along a piece of state with the current pathname so the page loaded will correctly set the initAnimation in the container div.

const AboutSubpage = ( prevPath ) => {

  ...

  if(prevPath.location.state){
    if(prevPath.location.state.prevPath === '/about/another-subpage'){
      initString = "enterRight"
    }

  ...
Enter fullscreen mode Exit fullscreen mode

This all works, but it feels convoluted. Especially with more than two animations to cycle through. If I wanted to have four exit variants I'd end up with this odd construction in every single Link element:

      // order: "exitTop", "exitRight", "exitBottom", "exitLeft"
      // we want to cylce to exitBottom here...
      if(exitAnimation === "exitTop"){
        cycleExitAnimation()
        cycleExitAnimation()
      }else if(exitAnimation === "exitLeft"){
        cycleExitAnimation()
        cycleExitAnimation()
        cycleExitAnimation()
      }else if(exitAnimation === "exitRight"){
        cycleExitAnimation()
      }
Enter fullscreen mode Exit fullscreen mode

(useCycle doesn't accept parameters, hence the weird multiple calls)

it's just a lot of redundancy for something that feels like it should easily achievable. Maybe there is a way that's more maintainable?

Cheers,
Willem