Framer Motion (React):如何排序初始、退出和布局动画?

Framer Motion (React): How to order initial, exit and layout animations?

我正在使用 framer-motion 为网格列中的变化设置动画。

这是我想要做的:

  1. 我在网格中有九个按钮(#db-wrapper 是网格容器)。

  1. 用户切换到“最大化”视图(props.minimize 更改)第四列被添加到网格中,已经存在的按钮移动到它们的新位置,为其余按钮留下空单元格.然后,新按钮从底部滑入,填充空单元格。网格中的按钮现在位于连续的单元格中。

  1. 现在用户切换回最小化视图。首先,所有要删除的按钮都应该滑到底部。
  2. 然后,其余按钮应移动到它们在网格中的新位置(它们的旧位置,如开头),以便它们填充连续的单元格。

(基本上,我想在用户切换回来后向后执行第 1 步和第 2 步)

我已经完成了第一步和第二步,下面是功能组件:

const DrumButtons = (props) => {
  const drumButtonsVariants = {
    hidden: {
      y: "140vh",
    },
    visible: {
      y: 0,
      transition: {
        type: "tween",
        duration: 1,
        delay: 0.1,
      },
    },
    exit: {
      y: "140vh",
      transition: {
        type: "tween",
        duration: 1,
        delay: 0.1,
      },
    },
  };

  let dbWrapperStyle = {};
  if (!props.minimized) {
    dbWrapperStyle = {
      gridTemplateColumns: "1fr 1fr 1fr 1fr",
    };
  }

  let singleWrapperStyle = {
    width: "100%",
    height: "100%",
  };
  let buttonTransition = { duration: 0.5, delay: 0.1 };
  return (
    <div id="db-wrapper" style={dbWrapperStyle}>
      <AnimatePresence>
        {props.buttonsarr.map((elem, i) => {
          return (
            <motion.div
              variants={drumButtonsVariants}
              initial="hidden"
              animate="visible"
              exit="exit"
              key={elem.press}
              style={singleWrapperStyle}
            >
              <motion.button
                layout
                transition={buttonTransition}
                key={elem.press}
                className="drum-pad"
                onClick={dbHandleClickWrapper(
                  props.changetext,
                  props.buttonsarr
                )}
                id={elem.name}
              >
                <span className="front">{elem.press.toUpperCase()}</span>
                <audio
                  key={elem.press.toUpperCase()}
                  id={elem.press.toUpperCase()}
                  src={props.buttonsarr[i].source}
                  preload="auto"
                  className="clip"
                ></audio>
              </motion.button>
            </motion.div>
          );
        })}
      </AnimatePresence>
    </div>
  );
};

到底是什么问题?

这是从“最大化”切换回“最小化”时当前发生的情况:

  1. 不再需要的按钮滑出到底部。同时,4 个网格列减少到 3 个列。因此,其余按钮将滑动到其在 3 列宽网格中的位置,为当前滑出但仍存在于 DOM.
  2. 中的按钮留下空单元格
  3. 删除其他按钮后,其余按钮再次移动以填充网格的连续单元格。

这是我试过的:

  1. 我试过将 when: "beforeChildren" 添加到 drumButtonsVariants 的转换中。没有任何变化。
  2. 我尝试过延迟,但从未达到预期的结果。如果我延迟布局动画,从 DOM 中移除按钮也会延迟,导致相同的不良结果。

你可以在这里查看我的完整代码:

(总支)https://github.com/Julian-Sz/FCC-Drum-Machine/tree/main

(有问题的版本)https://github.com/Julian-Sz/FCC-Drum-Machine/tree/284606cac7cbc4bc6e13bf432c563eab4814d370

随时复制和分叉此存储库!

诀窍是为 dbWrapperStyle 使用 State。 那么,AnimatePresenceonExitComplete就可以用了。

当用户再次切换到最小化时会发生这种情况:

  1. 移除的按钮滑出(但它们仍在DOM - 占据的网格单元格中)
  2. 现在它们从 DOM 中删除,并且 onExitComplete 触发:网格列更改为 3 列并且组件重新呈现。 DOM 中的移除和网格更改相继发生(没有任何延迟)- 导致动画流畅!

这是新组件:

const DrumButtons = (props) => {
  const drumButtonsVariants = {
    visible: {
      y: 0,
      transition: {
        type: "tween",
        duration: 0.8,
        delay: 0.1,
      },
    },
    exit: {
      y: "140vh",
      transition: {
        type: "tween",
        duration: 0.8,
        delay: 0.1,
      },
    },
  };

  // BEFORE------------
  // let dbWrapperStyle = {};
  // if (!props.minimized) {
  //   dbWrapperStyle = {
  //     gridTemplateColumns: "1fr 1fr 1fr 1fr",
  //   };
  // }

  // AFTER-------------
  const [dbWrapperStyle, setWrapperStyle] = useState({});
  useEffect(() => {
    if (!props.minimized) {
      setWrapperStyle({ gridTemplateColumns: "1fr 1fr 1fr 1fr" });
    }
  }, [props.minimized]);

  let singleWrapperStyle = {
    width: "100%",
    height: "100%",
  };

  let buttonTransition = {
    type: "spring",
    duration: 0.9,
    delay: 0.1,
    bounce: 0.5,
  };

  return (
    <div id="db-wrapper" style={dbWrapperStyle}>
      <AnimatePresence
        onExitComplete={() => {
          setWrapperStyle({ gridTemplateColumns: "1fr 1fr 1fr" });
        }}
      >
        {props.buttonsarr.map((elem, i) => {
          return (
            <motion.div
              variants={drumButtonsVariants}
              initial="exit"
              animate="visible"
              exit="exit"
              key={elem.press}
              style={singleWrapperStyle}
            >
              <motion.button
                layout
                transition={buttonTransition}
                key={elem.press}
                className="drum-pad"
                onClick={dbHandleClickWrapper(
                  props.changetext,
                  props.buttonsarr
                )}
                id={elem.name}
              >
                <span className="front">{elem.press.toUpperCase()}</span>
                <audio
                  key={elem.press.toUpperCase()}
                  id={elem.press.toUpperCase()}
                  src={props.buttonsarr[i].source}
                  preload="auto"
                  className="clip"
                ></audio>
              </motion.button>
            </motion.div>
          );
        })}
      </AnimatePresence>
    </div>
  );
};