DEV Community

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

Collapse
 
sam_piggott profile image
Sam Piggott

Hey Willem!

Glad you enjoyed the guide :)

If I understand you correctly, to get your desired behaviour, it should be as straightforward as adjusting the initial, animate and exit props to fit your needs for each page separately. In the example I describe in the original post, we use the same props for both pages in order to gain the same transition when navigating through each page.

However, if you wanted to adjust, say, Page2's transition animation in, you simply need to change the properties for the Page2 component!

Here's an example. Below, Page1 would enter with a scaleY effect (stretching vertically), and Page2 would enter with a scaleX effect (stretching horizontally).

const Page1 = () => {
  return (
    <motion.div
      initial={{ scaleY: 0 }}
      animate={{ scaleY: 1 }}
      exit={{ scaleY: 0 }}
      transition={{ duration: 0.5 }}
    >
      <div style={{ ...styles.page, ...styles.page1 }}>
        <p style={styles.copy}>This is page 1</p>
        <Link style={{ ...styles.copy, ...styles.link }} to="/page2">
          Go to Page 2
        </Link>
      </div>
    </motion.div>
  );
};

const Page2 = () => {
  return (
    <motion.div
      initial={{ scaleX: 0 }}
      animate={{ scaleX: 1 }}
      exit={{ scaleX: 0 }}
      transition={{ duration: 0.5 }}
    >
      <div style={{ ...styles.page, ...styles.page2 }}>
        <p style={styles.copy}>This is page 2</p>
        <Link style={{ ...styles.copy, ...styles.link }} to="/page1">
          Go to Page 1
        </Link>
      </div>
    </motion.div>
  );
};
Enter fullscreen mode Exit fullscreen mode

You're not limited to just scaleX or scaleY - it can be any combination of any CSS properties! I'm just using these as a simple example.

Hope that helps!

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