How can i make a child animation happen every time the parent animation is beginning using framer motion & react

2k Views Asked by At

I'm trying to make a squeezing bubble animation on repeat, using framer motion & react, but I cant make the squeeze animation happen every time the movement animation is beginning.

instead only the first time the animations run it works but after that only the movement animation repeats itself, if I try to repeat the squeeze animation it just gets out of order

import React from "react";
import styled from "styled-components";
import { motion } from "framer-motion";

const Bubble = () => {
  const shapeVariants = {
    hidden: {
      height: 450,
      width: 50,
    },
    visible: {
      height: 250,
      width: 250,
      transition: {
        type: "spring",
        bounce: 1,
        stiffness: 700,
        ease: "easeIn",
      },
    },
  };
  const MoveVariants = {
    hidden: {
      y: 1300,
    },
    visible: {
      y: -280,
      transition: {
        duration: 2,
        ease: "easeIn",
        repeat: Infinity,
      },
    },
  };

  return (
    <motion.div variants={MoveVariants} initial={"hidden"} animate={"visible"}>
      <RoundDiv
        onAnimationComplete={(definition) => console.log(definition)}
        variants={shapeVariants}
      />
    </motion.div>
  );
};

const RoundDiv = styled(motion.div)`
  height: 250px;
  width: 250px;
  background-color: #05386b;
  border-radius: 50%;
`;

export default Bubble;
2

There are 2 best solutions below

0
On

The cascading animation in framer-motion is powered by the variant being propagated through the children.

You are running into this setback because you are only animating to the `visible variant once. Thus the variant propagation only happens once.

Potential Solutions

  1. Dumb solution: include a duration into the shapeVariant and make it also repeat then manually time your animation to where you need it. This isn't optimal because you probably want your bubble animation to be type spring?
 const shapeVariants = {
    hidden: {
      height: 450,
      width: 50,
    },
    visible: {
      height: 250,
      width: 250,
      transition: {
        type: "spring",
        bounce: 1,
        stiffness: 700,
        ease: "easeIn",
        duration: 2,
        repeat: Infinity
      },
    },
  };
  1. Alternatively you could control your animation with an effect that would use setTimeout or something to change the variant of the parent over and over to get the cascading effect
1
On

You just needed to add to your shapeVariants transition to get them to sync up.

import React from "react";
import styled from "styled-components";
import { motion } from "framer-motion";

const Bubble = () => {
    const shapeVariants = {
        hidden: {
            height: 450,
            width: 50,
        },
        visible: {
            height: 250,
            width: 250,
            transition: {
                type: "spring",
                bounce: 1,
                stiffness: 700,
                ease: "easeIn",
                duration: 2, /* new */
                repeat: Infinity, /* new */
            },
        },
    };
    const MoveVariants = {
        hidden: {
            y: 1300,
        },
        visible: {
            y: -280,
            transition: {
                duration: 2,
                ease: "easeIn",
                repeat: Infinity,
            },
        },
    };
    return (
        <motion.div
            variants={MoveVariants}
            initial={"hidden"}
            animate={"visible"}
        >
            <RoundDiv
                onAnimationComplete={(definition) => console.log(definition)}
                variants={shapeVariants}
            />
        </motion.div>
    );
};

const RoundDiv = styled(motion.div)`
    height: 250px;
    width: 250px;
    background-color: #05386b;
    border-radius: 50%;
`;

export default Bubble;

I would also recommend using originX and originY to manipulate the spring transition on the bubble otherwise it will animate the bounce based on the top left corner. I would use percentage values such as originX: "50%" but it depends on the effect you are looking for.