mcpField
F
framer-motion-layout

framer-motion-layout

github.com/c-jeril设计与 UI10788 次安装30 周前更新
代码仓库

关于 framer-motion-layout

framer-motion-layout 是使用 Framer Motion 实现布局和共享元素动画的技能,涵盖自动布局变化、layoutId 转场、AnimatePresence 退出动画和可重排列表。

主要功能

  1. 01自动播放位置和尺寸动画
  2. 02使用 layoutId 实现共享元素转场
  3. 03AnimatePresence 退出动画
  4. 04可重排列表模式
  5. 05协调组件挂载与卸载动画

适用场景

  1. 01列表顺序变化时为项目添加动画
  2. 02在页面之间转场卡片
  3. 03让模态框从来源项目转场出现
  4. 04安排元素退出与进入的先后

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/c-jeril/framer-motion-skills --skill framer-motion-layout

安装后在 Claude Code 中输入/framer-motion-layout确认已加载。

详细介绍

Framer Motion 布局动画

何时使用此技能

在实现共享元素过渡、重排布局动画或协调挂载/卸载动画时使用。当用户询问 Framer Motion 布局动画、layoutId 或 AnimatePresence 时使用。

相关技能: 核心动画请使用 framer-motion-core;变体请使用 framer-motion-variants;React 集成请使用 framer-motion-react。

Layout 属性

layout 属性可在布局发生变化时自动启用位置动画:

<motion.div layout>
  {items.map(item => (
    <motion.div key={item.id} layout />
  ))}
</motion.div>
布局模式
模式行为
true为位置和尺寸添加动画
"position"仅为位置添加动画
"size"仅为尺寸添加动画
<motion.div layout="position" />
<motion.div layout="size" />

用于共享元素过渡的 layoutId

layoutId 可在不同组件中的元素之间实现平滑过渡:

页面过渡
// Page A
function CardA() {
  return <motion.div layoutId="card" />;
}

// Page B
function CardB() {
  return <motion.div layoutId="card" />;
}

当 CardA 卸载、CardB 挂载且二者具有相同的 layoutId 时,Framer Motion 会让元素在不同位置之间平滑过渡。

模态覆盖层
function ListItem({ item, onClick }) {
  return (
    <motion.div layoutId={`item-${item.id}`} onClick={onClick}>
      {item.name}
    </motion.div>
  );
}

function Modal({ item }) {
  return (
    <motion.div layoutId={`item-${item.id}`}>
      <h2>{item.name}</h2>
      <p>{item.description}</p>
    </motion.div>
  );
}

用于布局的 AnimatePresence

AnimatePresence 可启用退出动画:

import { AnimatePresence, motion } from "framer-motion";

function TodoList({ todos }) {
  return (
    <motion.ul>
      <AnimatePresence>
        {todos.map(todo => (
          <motion.li
            key={todo.id}
            layout
            initial={{ opacity: 0, x: -20 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: 20 }}
          />
        ))}
      </AnimatePresence>
    </motion.ul>
  );
}
AnimatePresence 模式
<AnimatePresence mode="wait">
  {isOpen && <Modal key="modal" />}
</AnimatePresence>
模式说明
"sync"所有动画同时运行(默认)
"wait"退出完成后再开始进入动画
"popLayout"立即将退出元素从布局中移除

可重排列表

将 layout 与拖动结合,创建可重排列表:

function ReorderableList({ items, setItems }) {
  return (
    <AnimatePresence>
      {items.map(item => (
        <motion.div
          key={item.id}
          layout
          drag
          dragConstraints={{ top: 0, bottom: 0 }}
          onDragEnd={({ info, target }) => {
            // Calculate new index and update
          }}
          initial={{ opacity: 0, scale: 0.8 }}
          animate={{ opacity: 1, scale: 1 }}
          exit={{ opacity: 0, scale: 0.8 }}
        />
      ))}
    </AnimatePresence>
  );
}

使用 Grid 的共享布局

function Grid({ items }) {
  return (
    <motion.div
      layout
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(auto-fill, minmax(100px, 1fr))",
        gap: 10
      }}
    >
      {items.map(item => (
        <motion.div
          key={item.id}
          layout
          style={{
            width: "100%",
            aspectRatio: 1,
            backgroundColor: item.color
          }}
        />
      ))}
    </motion.div>
  );
}

退出动画

元素必须定义退出状态,AnimatePresence 才能生效:

<motion.div
  initial={{ opacity: 0, scale: 0 }}
  animate={{ opacity: 1, scale: 1 }}
  exit={{ opacity: 0, scale: 0 }}
/>
带布局的退出动画
<motion.div
  layout
  exit={{ opacity: 0, x: -100, transition: { duration: 0.3 } }}
/>

使用 layoutId 实现交叉淡化

当同一层级的多个元素共享一个 layoutId 时:

function Toggle() {
  const [showA, setShowA] = useState(true);

  return (
    <AnimatePresence mode="popLayout">
      {showA ? (
        <motion.div
          key="a"
          layoutId="shape"
          style={{ backgroundColor: "red" }}
        />
      ) : (
        <motion.div
          key="b"
          layoutId="shape"
          style={{ backgroundColor: "blue", borderRadius: "50%" }}
        />
      )}
    </AnimatePresence>
  );
}

元素会在不同状态之间平滑变形,包括边框圆角。

最佳实践

  • ✅ 在路由/页面之间实现共享元素过渡时使用 layoutId。
  • ✅ 使用 AnimatePresence 包裹带动画的列表,以实现退出动画。
  • ✅ 使用 layout 属性自动实现位置动画。
  • ✅ 使用 AnimatePresence mode="popLayout",实现平滑重排。
  • ✅ 为使用 AnimatePresence 的组件定义 exit 状态。
  • ✅ 当子元素需要进行位置动画时,在父容器上使用 layout。

请勿

  • ❌ 忘记使用 AnimatePresence 包裹有条件渲染的动画组件。
  • ❌ 在同一层级的多个元素上使用相同的 layoutId。
  • ❌ 忘记为即将卸载的组件定义退出状态。
  • ❌ 未为子元素设置正确的 key 就使用布局动画。
  • ❌ 同时为过多布局元素添加动画——请将它们分组或错开执行。
了解更多

https://www.framer.com/motion/layout-animations/ https://www.framer.com/motion/animate-presence/

评论

支持 Markdown