Search code examples
reactjsframer-motion

Slideshow effect with Framer Motion


When some prop in my component changes in framer motion, I would like to fade that property out, then fade the "new" property value in?

Here's the timeline of the animation as I imagine it:

  • 0: Prop Value Changes, old value start to fade out
  • .5: New value visible
  • 1: New value finishes fading in

But the only way I see to do this with framer is to use Timeouts. Is there some way other than using timeouts to achieve this effect?

Codesandbox

import { motion } from "framer-motion";
import { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [seconds, setSeconds] = useState(0);
  const [anim, setAnim] = useState("in");
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds((seconds) => seconds + 1);
      setAnim("in");
      setTimeout(() => {
        setAnim("out");
      }, 500);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  const variants = {
    out: {
      opacity: 0
    },
    in: {
      opacity: 1,
      transition: {
        duration: 0.5
      }
    }
  };

  return (
    <motion.div
      animate={anim}
      variants={variants}
      className="App"
      style={{ fontSize: 100 }}
    >
      {seconds}
    </motion.div>
  );
}


Solution

  • You can do this with AnimatePresence.

    Wrap your motion.div with an AnimatePresence tag, and use the seconds as a unique key for your div. The changing key will trigger AnimatePresence to animate the div in and out each time it changes (because new key means it's a different element).

    To get this to work, you'll need to define your animations on the initial, animate, and exit props.

    You'll also want to be sure to set the exitBeforeEnter prop on AnimatePresence so the fade out animation completes before the fade in starts.

    Sandbox Example

    export default function App() {
      const [seconds, setSeconds] = useState(0);
    
      useEffect(() => {
        const interval = setInterval(() => {
          setSeconds((seconds) => seconds + 1);
        }, 1000);
        return () => clearInterval(interval);
      }, []);
    
    
      return (
        <AnimatePresence exitBeforeEnter>
          <motion.div
            initial={{opacity:0}}
            animate = {{opacity: 1, transition:{duration: 0.5}}}
            exit={{opacity: 0 }}
            
            className="App"
            style={{ fontSize: 100 }}
            key={seconds}
          >
            {seconds}
          </motion.div>
        </AnimatePresence>
      );
    }