Niklas
Niklas

Reputation: 1798

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,
    }}
  />
)

Upvotes: 2

Views: 7746

Answers (3)

Josiah
Josiah

Reputation: 73

Use <AbsencePresence> with mode wait and initial set to false

<AnimatePresence mode="wait" initial={false}>
  {itemKey === 0 && (
    <motion.div
      key="0"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
  {itemKey === 1 && (
    <motion.div
      key="1"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
</AnimatePresence>

Upvotes: 1

Joshua Wootonn
Joshua Wootonn

Reputation: 1276

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

Upvotes: 5

Bart Krakowski
Bart Krakowski

Reputation: 1670

Your components should return <motion.div> (or <motion.section>, <motion.span> etc.). And in the page component you should use <AnimatePresence /> component (like in the example):

<AnimatePresence initial={false} custom={direction}>
  {COMPONENTS}
</AnimatePresence>

Then you have to decide which component will appear:

{page === 0 && <ComponentOne />}
{page === 1 && <ComponentTwo/>}
{page === 2 && <ComponentThree/>}

The animations you can control with variants.

You can see a quick demo here: https://codesandbox.io/s/quizzical-hypatia-7wqjc?file=/src/App.tsx

Upvotes: 0

Related Questions