关于 framer-motion-layout
framer-motion-layout 是使用 Framer Motion 实现布局和共享元素动画的技能,涵盖自动布局变化、layoutId 转场、AnimatePresence 退出动画和可重排列表。
主要功能
- 01自动播放位置和尺寸动画
- 02使用 layoutId 实现共享元素转场
- 03AnimatePresence 退出动画
- 04可重排列表模式
- 05协调组件挂载与卸载动画
适用场景
- 01列表顺序变化时为项目添加动画
- 02在页面之间转场卡片
- 03让模态框从来源项目转场出现
- 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