How to conditionally loop an animation in Framer Motion?

35 Views Asked by At

I created an animation using Framer Motion's useAnimation(). I want to loop the animation using while loop with the condition !isOpen to run the animation endlessly until the user clicks the element. However, when isOpen state is true the screen freezes and it endlessly console.log('hello').

How can run the animation endlessly without freezing my screen? If there is a more appropriate approach other than this, please let me know. Thanks!

const [isOpen, setIsOpen] = useState(false);
const controls = useAnimation();

const idleAnimation = async () => {
    while (!isOpen) {
        await controls.start({
            scaleY: [1, 0.75, 1.1],
            transition: {
                duration: 0.5,
                ease: "easeIn",
                delay: 1
            }
        });

        await controls.start({
            y: [0, -75, 0],
            transition: {
                ease: "easeInOut",
                duration: 1,
                y: {
                    duration: 0.85,
                }
            }
        });

        await controls.start({
            scaleY: [1.1, 0.95, 1],
            transition: {
                duration: 0.5,
                ease: "easeOut"
            }
        });

        console.log('hello')
    }
};

<motion.div
    animate={isOpen ? null : controls}
    style={{
        originY: "100%"
    }}
    onClick={() => { setIsOpen(true) }}
    className="Gift"
>
    // Code
</motion.div>

enter image description here

1

There are 1 best solutions below

0
G Sriram On

Not the most efficient way, but you can run it this way.

const runAnimation = async () => {
     await controls.start({
            scaleY: [1, 0.75, 1.1],
            transition: {
                duration: 0.5,
                ease: "easeIn",
                delay: 1
            }
        });

        await controls.start({
            y: [0, -75, 0],
            transition: {
                ease: "easeInOut",
                duration: 1,
                y: {
                    duration: 0.85,
                }
            }
        });

        await controls.start({
            scaleY: [1.1, 0.95, 1],
            transition: {
                duration: 0.5,
                ease: "easeOut"
            }
        });
        if(open) runAnimation(); // Restart the animation series once completed
};

useEffect(() => {
    runAnimation();
}, [open]);

However if you don't mind all animations running at once or if you want to tweak it a bit, you can make it more efficient by using the repeat function.

await controls.start({
  scaleY: [1, 0.75, 1.1],
  transition: {
    duration: 0.5,
    ease: "easeIn",
    repeat: Infinity,
    repeatType: "reverse",
    delay: 1
  }
});

await controls.start({
  y: [0, -75, 0],
  transition: {
    ease: "easeInOut",
    duration: 1,
    repeat: Infinity,
    repeatType: "reverse",
    y: {
      duration: 0.85,
    }
 }
});

await controls.start({
  scaleY: [1.1, 0.95, 1],
  transition: {
    duration: 0.5,
    repeat: Infinity,
    repeatType: "reverse",
    ease: "easeOut"
  }
});