Search code examples
reactjsframer-motion

How can i switch between react components using framer motion?


In my react app i need to switch between components like in a carousel. I found this example to build an image carousel only using framer motion: https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?file=/src/Example.tsx:1715-1725

I want to adapt this to switching between components. At the moment my page looks something like this:

const variants = {
  enter: (direction: number) => {
    return {
      x: direction > 0 ? 100 : -100,
      opacity: 0,
    }
  },
  center: {
    zIndex: 1,
    x: 0,
    opacity: 1,
  },
  exit: (direction: number) => {
    return {
      zIndex: 0,
      x: direction < 0 ? 100 : -100,
      opacity: 0,
    }
  },
}

const Page = () => {

const [[page, direction], setPage] = useState([0, 0])
const paginate = (newDirection: number) => {
  setPage([page + newDirection, newDirection])
}
return (
   <motion.div
     key={page}
     custom={direction}
     variants={variants}
     initial="enter"
     animate="center"
     exit="exit"
   >
     <!-- my components, between which I want to switch, should appear here -->
   </motion.div>
 )
}

how would I have to build the logic to be able to switch dynamic between my components (slides)? In the codesandbox example the images were changed via an array:

const imageIndex = wrap(0, images.length, page);

<motion.img key={page} src={images[imageIndex]} />

How could i do that to switch between jsx elements?

Edit

The answer from Joshua Wootonn is correct, but you need to add the custom prop also to the TestComp to get the animation working with dynamic variants like this:

const TestComp = ({ bg }: { bg: string }) => (
  <motion.div
    custom={direction}
    variants={variants}
    initial="enter"
    animate="center"
    exit="exit"
    transition={{
      x: { type: "spring", stiffness: 100, damping: 30 },
      opacity: { duration: 0.2 },
    }}
    className="absolute w-full h-full"
    style={{
      background: bg,
    }}
  />
)

Solution

  • A couple of things were missing from the above answer to get exit animations working.

    1. If you want exit animations to work within AnimationPresense you need to set keys on its children
            <AnimatePresence initial={false} custom={direction}>
              {page === 0 && <TestComp key="0" bg="rgb(171, 135, 255)" />}
              {page === 1 && <TestComp key="1" bg="rgb(68, 109, 246)" />}
              {page === 2 && <TestComp key="2" bg="rgb(172, 236, 161)" />}
            </AnimatePresence>
    
    1. If you want to animate something in while something is still animating out without having massive content shifting, you need to take them out of the flow. (use absolute positioning and wrap with relatively positioned container)
          <div style={{ position: "relative", height: "300px", width: "300px" }}>
            <AnimatePresence initial={false} custom={direction}>
              ...
            </AnimatePresence>
          </div>
    

    and on the child components

      height: 100%;
      width: 100%;
      position: absolute;
    

    Working codesandbox: https://codesandbox.io/s/framer-motion-carousel-animation-wetrf?file=/src/App.tsx:658-708