javascriptreactjsanimationmotion

Trigger animation when another starts in motion


I am using motion to animate a background color whenever the variable colorHex changes, which works nicely. I would also like to scale up and back down each time the color changes. For this I've used scale: [1, 2, 1] however because the value never changes, it only runs on the initial animation. How can I ensure it retriggers whenever colorHex changes?

<motion.div
  transition={{ duration: 0.3, delay: offset * 0.1, ease: "easeOut" }}
  animate={{
    backgroundColor: colorHex,
    scale: [1, 2, 1],
  }}
  ...

Note that the only work around I've found is to set the scale to a new (very slightly different) value when the color value changes.


Solution

  • First, you can trigger "manual" animations by using useAnimate:

      const [colorHex, setColorHex] = useState(getRandomColor());
      const [scope, animate] = useAnimate();
    
      const handleClick = () => {
        setColorHex(getRandomColor());
        animate(scope.current, { scale: [1, 2, 1] });
      };
    
      return (
        <motion.div
          ref={scope}
          onClick={handleClick}
          transition={{ duration: 0.3, delay: offset * 0.1, ease: "easeOut" }}
          animate={{
            backgroundColor: colorHex,
            scale: [1, 2, 1],
          }}
        >
          Hello World!
        </motion.div>
     )
    

    Now that you have control on how to trigger the animation, you can just use React.useEffect to listen for changes on your colorHex:

    export default function App() {
      const [colorHex, setColorHex] = useState(getRandomColor());
      const [scope, animate] = useAnimate();
    
      useEffect(() => {
        animate(scope.current, { scale: [1, 2, 1] });
      }, [colorHex]);
    
      const handleClick = () => {
        setColorHex(getRandomColor());
      };
    
      return (
        <motion.div
          onClick={handleClick}
          ref={scope}
          transition={{ duration: 0.3, delay: offset * 0.1, ease: "easeOut" }}
          animate={{
            backgroundColor: colorHex,
            scale: [1, 2, 1],
          }}
        >
          Hello World!
        </motion.div>
      );
    }
    

    Here is a small repro with the full code.