Search code examples
framer-motion

How Can I Trigger a CSS Animation After a Framer Motion Animation Completes?


I have a component that's inside a Framer Motion div. That component has a CSS animation, but the timing for the animation is off, because the Framer Motion animation takes a variable amount of time to complete.

What I'd like is someway to tell Framer Motion "do your animation, but then tell me when you are done, so I can trigger my other animation". Is that possible?

The code for the Framer Motion element is:

 <motion.section
      initial={{ x: 100, opacity: 0 }}
      animate={{ x: 0, opacity: 1 }}
      transition={{ when: 'beforeChildren'}}
    >
      <MyComponent/>
    </motion.section>

MyComponent has its own CSS animation, triggered with a useEffect and some DOM manipulation to add a class:

 const MyComponent = () => {
      useEffect(() =>{
        const id = getFromUrl();
        document.getElementById(id).classList.add('animation-class');
      }, [])
      // ... rest of component ...
 }

Here's the CSS for the animation:

 .purple-highlight {
   animation: glow 5s infinite alternate;
   animation-iteration-count: 1;
   animation-timing-function: linear;
 }

 @keyframes glow {
   0% {
     box-shadow: 8px 0 5px 1px var(--purple-500);
   }
   100% {
     box-shadow: 0 0 0 0 var(--purple-500);
   }
 }

The problem is, the 5s in the animation CSS is either too short or too long. If the Framer Motion stuff takes awhile, the CSS animation finishes before it even becomes visible ... but if I set it to 20s then when Framer finishes more quickly, the animation lasts too long.

Hopefully there's some way to trigger code after Framer Motion is done with its animation?


Solution

  • Framer Motion adds some animation events to each motion element. You can add the onAnimationComplete prop to your motion element that will run when each animation is complete.

    <motion.section
      initial={{ x: 100, opacity: 0 }}
      animate={{ x: 0, opacity: 1 }}
      transition={{ when: 'beforeChildren'}}
      onAnimationComplete={() => alert('Animation Complete!')}
    >
      <MyComponent/>
    </motion.section>
    

    Here is a working example.