Framer Motion:修复 child 使用 AnimateSharedLayout 设置动画时的失真

Framer Motion: Fix child distortion when animated with AnimateSharedLayout

我想在卡片展开时使用 AnimateSharedLayout 交叉淡入淡出(div 与 children)。

但是,字体在布局动画中变形。

Here is a link to a codesandbox.

这是我的代码(我使用的是 Tailwind CSS):

这是 Projects 组件,它在 CSS 网格中包含所有卡片:

export default function Projects() {
  const projectObjArray = [
    {
      title: "Project 1",
      description: "some description",
      background: "#f00",
      link: ""
    },
    {
      title: "Project 2",
      description: "some description",
      background: "#f00",
      link: ""
    },
    {
      title: "Project 3",
      description: "some description",
      background: "#f00",
      link: ""
    }
  ];

  const [expanded, setExpanded] = useState(undefined);

  return (
    <div>
      <h1 className="text-6xl mb-5 md:text-left md:ml-10 pb-5">My Projects</h1>
      <div className="grid grid-cols-1 md:grid-cols-3 gap-10">
        {projectObjArray.map((el, index) => {
          if (index !== expanded) {
            return (
              <ProjectTemplate
                projectobj={el}
                key={el.title}
                index={index}
                setexpanded={setExpanded}
              />
            );
          } else {
            return (
              <ProjectTemplateExpanded
                projectobj={el}
                key={el.title}
                index={index}
                setexpanded={setExpanded}
              />
            );
          }
        })}
      </div>
    </div>
  );
}

单击卡片时,其索引会保存在 expanded 状态。 然后,ProjectTemplateExpanded 组件将在该位置呈现,而不是 ProjectTemplate.

以下是这些组件:

export default function ProjectTemplate(props) {
  return (
    <motion.div
      initial={{ borderRadius: "100px" }}
      animate={{ borderRadius: "50px" }}
      className="w-full h-56 flex flex-col justify-center"
      style={{ background: props.projectobj.background }}
      onClick={() => {
        props.setexpanded(props.index);
      }}
      layoutId={`Container${props.projectobj.title}`}
      transition={{ duration: 2 }}
    >
      <motion.h1
        layoutId={`Title${props.projectobj.title}`}
        // layout
        transition={{ duration: 2 }}
        className="text-2xl"
      >
        {props.projectobj.title}
      </motion.h1>
      <motion.p
        layoutId={`Description${props.projectobj.title}`}
        // layout
        transition={{ duration: 2 }}
        className="text-1xl"
      >
        {props.projectobj.description}
      </motion.p>
    </motion.div>
  );
}
export default function ProjectTemplateExpanded(props) {
  return (
    <>
      <div className="h-56"></div>
      <motion.div
        className="w-full fixed top-0 left-0 right-0 bottom-0 z-50 flex flex-col justify-center items-center"
        style={{ background: "rgba(0, 0, 0, 0.5)" }}
        onClick={() => {
          props.setexpanded(undefined);
        }}
      >
        <motion.div
          layoutId={`Container${props.projectobj.title}`}
          transition={{ duration: 2 }}
          initial={{ borderRadius: "50px" }}
          animate={{ borderRadius: "100px" }}
          style={{
            background: props.projectobj.background,
            width: "80vw",
            height: "50vh"
          }}
          onClick={(e) => {
            e.stopPropagation();
          }}
          className="flex flex-col justify-center"
        >
          <motion.h1
            layoutId={`Title${props.projectobj.title}`}
            // layout
            transition={{ duration: 2 }}
            className="text-2xl"
          >
            {props.projectobj.title}
          </motion.h1>
          <motion.p
            layoutId={`Description${props.projectobj.title}`}
            // layout
            transition={{ duration: 2 }}
            className="text-1xl"
          >
            {props.projectobj.description}
          </motion.p>
        </motion.div>
      </motion.div>
    </>
  );
}

Projects 组件用 AnimateSharedLayout 包装。

export default function App() {
  return (
    <div className="App text-white pt-5 px-5">
      <AnimateSharedLayout type="crossfade">
        <Projects />
      </AnimateSharedLayout>
    </div>
  );
}

documentation 状态:

To correct distortion on immediate children, add layout to those too.

所以我尝试在 h1 和 p 标签上的 layoutId 旁边添加布局道具,但这并没有改变任何东西。

This blog post 声明比例校正应用于任何参与共享元素转换的组件(这里就是这种情况,因为在 children 上设置了 layoutId)

我错过了什么?

我已将传递给布局 属性 的值更改为“位置”。这样文本保持相同的风格。我还将 ProjectTemplateExpanded 函数中的高度和宽度样式从样式 属性 移动到动画 属性 以获得更流畅的动画。我使用 W3Schools 的 @keyframes 规则信息作为指南。尝试以下操作:

export default function ProjectTemplate(props) {
  return (
    <motion.div
      initial={{ borderRadius: "100px" }}
      animate={{ borderRadius: "50px" }}
      className="w-full h-56 flex flex-col justify-center"
      style={{ background: props.projectobj.background }}
      onClick={() => {
        props.setexpanded(props.index);
      }}
      layoutId={`Container${props.projectobj.title}`}
      transition={{ duration: 2 }}
    >
      <motion.h1
        layoutId={`Title${props.projectobj.title}`}
        layout="position"
        transition={{ duration: 2 }}
        className="text-2xl"
      >
        {props.projectobj.title}
      </motion.h1>
      <motion.p
        layoutId={`Description${props.projectobj.title}`}
        layout="position"
        transition={{ duration: 2 }}
        className="text-1xl"
      >
        {props.projectobj.description}
      </motion.p>
    </motion.div>
  );
}

export default function ProjectTemplateExpanded(props) {
  return (
    <>
      <div className="h-56"></div>
      <motion.div
        className="w-full fixed top-0 left-0 right-0 bottom-0 z-50 flex flex-col justify-center items-center"
        style={{ background: "rgba(0, 0, 0, 0.5)" }}
        onClick={() => {
          props.setexpanded(undefined);
        }}
      >
        <motion.div
          layoutId={`Container${props.projectobj.title}`}
          transition={{ duration: 2 }}
          initial={{ borderRadius: "50px" }}
          animate={{ borderRadius: "100px",
            width: "80vw",
            height: "50vh" }}
          style={{
            background: props.projectobj.background,
          }}
          onClick={(e) => {
            e.stopPropagation();
          }}
          className="flex flex-col justify-center"
        >
          <motion.h1
            layoutId={`Title${props.projectobj.title}`}
            layout="position"
            transition={{ duration: 2 }}
            className="text-2xl"
          >
            {props.projectobj.title}
          </motion.h1>
          <motion.p
            layoutId={`Description${props.projectobj.title}`}
            layout="position"
            transition={{ duration: 2 }}
            className="text-1xl"
          >
            {props.projectobj.description}
          </motion.p>
        </motion.div>
      </motion.div>
    </>
  );
}

参考文献:

@keyframes 规则 - CSS 动画。 W3学校。 https://www.w3schools.com/css/css3_animations.asp。 (2021 年 8 月 24 日访问)。

解决办法是在容器上放items-center。然后,h1p 元素的宽度将在动画期间得到更正。