Claude Code 与 Framer Motion:Motion for React 实战指南
用 Claude Code 实装 Framer Motion/Motion for React:提示词、可运行代码、常见坑、无障碍与验证步骤。
很多开发者仍然用 Framer Motion 这个关键词搜索,但 2026 年的官方 React 文档已经把它称为 Motion for React。新项目建议安装 motion,并从 motion/react 引入 React API。维护旧项目时,如果仓库已经统一使用 framer-motion,不要在同一个文件里随意混用。本文以官方 Motion for React 文档为基准,讲如何让 Claude Code 生成可维护的动画代码。
Claude Code 很适合快速补齐交互动效,但它不会自动理解产品边界。只说“让这个页面更顺滑”,通常会得到过度的弹跳、缺少 Reduced Motion 的实现,或者因为父组件提前卸载导致退出动画失效。Masa 在一个内部仪表盘上试过这个流程:第一版视觉很炫,但卡片移动过大,通知没有 exit,滚动头图在低性能电脑上明显卡顿。真正有用的做法,是先给 Claude Code 明确目标、范围、无障碍要求和验证方式。
本文面向已经会写 React 组件、但经常用 CSS 手工调整动画的开发者。下面的 TSX 示例在安装 npm install motion 后可以直接放入 React 项目。Next.js 或 Astro 中使用时,请把动画部分放到客户端组件里。
先固定设计约束
动画不是装饰,而是帮助用户理解状态变化。让 Claude Code 动手之前,先写清楚四件事。
| 约束 | 交给 Claude Code 的内容 | 审查时看什么 |
|---|---|---|
| 目的 | 哪个状态变化需要被解释 | 动画是否真的提升理解 |
| 范围 | 允许修改和禁止修改的文件 | 是否混入无关重构 |
| API | 使用 motion/react,支持 Reduced Motion | import 和行为是否符合官方文档 |
| 验证 | 手动操作、键盘、低速设备 | 是否按真实使用路径测试 |
可以把工作流理解为:
目标与约束
-> Claude Code 提示词
-> Motion for React 实装
-> 手动验证与无障碍验证
-> 有针对性的二次提示
建议的第一条提示词如下。
请为这个现有 React 组件加入 Motion for React 动画。
要求:
- 新增 Motion API 从 `motion/react` 引入
- 不改数据获取、表单提交、路由逻辑
- 支持 Reduced Motion
- 优先使用 opacity 与 transform
- 避免 layout shift
- 输出变更后需要手动验证的清单
这类提示能让 Claude Code 产出更容易 review 的差分。特别是 import 来源要说清楚,因为网上仍有大量旧文章使用 framer-motion。
用例1:卡片列表的自然入场
仪表盘、文章列表、功能卡片适合使用轻量 stagger。目标不是让每张卡都表演,而是建立阅读顺序。Masa 的验证结果是,6 张卡片时 0.06 到 0.09 秒的错开比较舒服,再长就会拖慢操作。
import { motion, useReducedMotion } from "motion/react";
type Feature = {
id: string;
title: string;
body: string;
metric: string;
};
const demoFeatures: Feature[] = [
{
id: "review",
title: "待审查",
body: "人类负责确认 Claude Code 生成的代码差分。",
metric: "8 项",
},
{
id: "motion",
title: "动画清理",
body: "用短过渡解释画面变化,而不是拖慢工作。",
metric: "14%",
},
{
id: "a11y",
title: "Reduced Motion",
body: "用户希望减少动态时,改用更安静的淡入淡出。",
metric: "已支持",
},
];
export function AnimatedFeatureCards({
items = demoFeatures,
}: {
items?: Feature[];
}) {
const shouldReduceMotion = useReducedMotion();
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: shouldReduceMotion ? 0 : 0.08,
},
},
};
const card = {
hidden: {
opacity: 0,
y: shouldReduceMotion ? 0 : 16,
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.32,
ease: "easeOut",
},
},
};
return (
<motion.section
aria-label="功能卡片"
variants={container}
initial="hidden"
animate="visible"
style={{
display: "grid",
gap: 16,
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
}}
>
{items.map((item) => (
<motion.article
key={item.id}
variants={card}
whileHover={shouldReduceMotion ? undefined : { y: -4 }}
style={{
border: "1px solid #d9e2ec",
borderRadius: 12,
padding: 20,
background: "#ffffff",
boxShadow: "0 8px 24px rgba(15, 23, 42, 0.08)",
}}
>
<p style={{ margin: 0, color: "#2563eb", fontWeight: 700 }}>
{item.metric}
</p>
<h3 style={{ margin: "8px 0", fontSize: 18 }}>{item.title}</h3>
<p style={{ margin: 0, lineHeight: 1.7, color: "#475569" }}>
{item.body}
</p>
</motion.article>
))}
</motion.section>
);
}
审查时重点看三点:key 是否稳定,移动是否集中在 transform,Reduced Motion 是否取消大位移。若需要二次提示,可以要求 Claude Code 在 12 张卡片时仍保持页面可立即操作。
用例2:通知的追加与删除
AnimatePresence 用来让即将离开 React 树的元素播放退出动画。官方 AnimatePresence 文档说明,直接子元素需要稳定 key。最常见的错误是把整个通知区域放进条件渲染,导致父级先消失,子元素没有机会执行 exit。
import { useState } from "react";
import { AnimatePresence, motion } from "motion/react";
type Toast = {
id: number;
message: string;
};
let nextToastId = 1;
export function AnimatedNotifications() {
const [toasts, setToasts] = useState<Toast[]>([
{ id: 0, message: "保存成功" },
]);
function addToast() {
const id = nextToastId++;
setToasts((current) => [
...current,
{ id, message: `后台任务 ${id} 已完成` },
]);
}
function dismissToast(id: number) {
setToasts((current) => current.filter((toast) => toast.id !== id));
}
return (
<div>
<button type="button" onClick={addToast}>
添加通知
</button>
<div
aria-live="polite"
style={{
position: "fixed",
right: 24,
top: 24,
display: "grid",
gap: 12,
width: "min(360px, calc(100vw - 48px))",
}}
>
<AnimatePresence initial={false} mode="popLayout">
{toasts.map((toast) => (
<motion.div
layout
key={toast.id}
initial={{ opacity: 0, x: 40, scale: 0.96 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
exit={{ opacity: 0, x: 40, scale: 0.96 }}
transition={{ duration: 0.2, ease: "easeOut" }}
style={{
borderRadius: 10,
border: "1px solid #cbd5e1",
background: "#ffffff",
padding: 16,
boxShadow: "0 12px 30px rgba(15, 23, 42, 0.16)",
}}
>
<p style={{ margin: "0 0 12px", color: "#0f172a" }}>
{toast.message}
</p>
<button type="button" onClick={() => dismissToast(toast.id)}>
关闭
</button>
</motion.div>
))}
</AnimatePresence>
</div>
</div>
);
}
这个组件要额外确认:通知为 0 时容器不被卸载,键盘用户可以关闭通知,aria-live 不会连续读出过多内容。通知是高频 UI,读屏和焦点体验比动画是否华丽更重要。
用例3:长页面的滚动进度
长文档、教程和登录页常需要告诉读者“现在读到哪里”。Motion 的 useScroll 能返回页面或元素的滚动进度,再用 useSpring 可以让进度条更顺滑。不要把每个区块都做成视差场景;Reduced Motion 开启时,应该去掉大幅移动,保留可读性。
import { useRef } from "react";
import {
motion,
useReducedMotion,
useScroll,
useSpring,
useTransform,
} from "motion/react";
const sections = [
{
title: "固定需求",
body: "让 Claude Code 修改前,先写明目标、目标文件和禁止事项。",
},
{
title: "少量移动",
body: "先用 opacity 与 transform,让动效服务于理解。",
},
{
title: "按真实路径验证",
body: "发布前检查低速设备、键盘操作和 Reduced Motion。",
},
];
export function ScrollReadingProgress() {
const articleRef = useRef<HTMLElement | null>(null);
const shouldReduceMotion = useReducedMotion();
const { scrollYProgress } = useScroll({
target: articleRef,
offset: ["start start", "end end"],
});
const scaleX = useSpring(scrollYProgress, {
stiffness: 120,
damping: 28,
mass: 0.2,
});
const y = useTransform(scrollYProgress, [0, 1], [0, -48]);
return (
<article ref={articleRef} style={{ position: "relative", padding: 24 }}>
<motion.div
aria-hidden="true"
style={{
position: "sticky",
top: 0,
zIndex: 10,
height: 4,
scaleX: shouldReduceMotion ? 1 : scaleX,
transformOrigin: "0% 50%",
background: "#2563eb",
}}
/>
<motion.header
style={{
y: shouldReduceMotion ? 0 : y,
padding: "56px 0 32px",
}}
>
<p style={{ color: "#2563eb", fontWeight: 700 }}>
Claude Code x Motion
</p>
<h2 style={{ fontSize: 36, margin: 0 }}>
随着阅读逐步呈现上下文的页面
</h2>
</motion.header>
<div style={{ display: "grid", gap: 24 }}>
{sections.map((section) => (
<section
key={section.title}
style={{
border: "1px solid #dbe4ee",
borderRadius: 12,
padding: 24,
background: "#ffffff",
}}
>
<h3>{section.title}</h3>
<p style={{ lineHeight: 1.8 }}>{section.body}</p>
</section>
))}
</div>
</article>
);
}
这个模式适合文档、入门引导、报告页。它的作用是帮助定位,不是填充页面。一个清晰的进度条,通常比五个滚动触发的飞入动画更可靠。
让 Claude Code 审查差分
实现之后,不要只看浏览器里是否会动。把下面的审查提示交给 Claude Code,让它从失败模式出发检查差分。
请把当前差分当作 Motion for React 实装来审查。
重点严格检查:
- 是否混用了 `motion/react` 和旧的 `framer-motion`
- `AnimatePresence` 的直接子元素是否有稳定 key
- Reduced Motion 是否关闭大位移、视差和类似自动播放的效果
- 是否频繁动画 width、height、top、left
- 手动测试步骤是否对应真实用户操作
这样 Claude Code 就不只是生成代码,也能成为第一轮 reviewer。因为它能看到周边文件,所以还可以发现 CSS 冲突、状态逻辑变化、测试不足等代码片段看不到的问题。
常见坑与发布检查
第一个坑是旧 import。遗留项目继续使用 framer-motion 并不一定错,但新实现应优先跟随 motion 包和 motion/react。第二个坑是把 exit 失效误判为 easing 问题。多数情况下,真正原因是父级过早卸载、key 使用数组下标,或者元素没有从 AnimatePresence 内离开。
第三个坑是过度相信 layout。它适合排序和尺寸变化,但未设置尺寸的图片、延迟加载的字体、突然变化的 Grid 列数仍会造成跳动。第四个坑是最后才考虑无障碍。Motion 的无障碍指南介绍了 Reduced Motion;只要使用大位移、视差或自动播放式效果,就应该在第一版中实现替代路径。第五个坑是只在高速开发机上测试。opacity 和 transform 通常安全,但大面积 blur、频繁 shadow、多个大图同时移动仍可能卡顿。
发布前至少确认:官方文档与 import 一致,每个动画都有产品目的,Reduced Motion 去掉大运动,AnimatePresence 子元素有稳定 key,键盘操作和 focus 没坏,滚动效果不影响阅读,并在 PR 或文章末尾留下验证结果。
相关主题可以继续阅读 Radix UI 与 Claude Code、shadcn/ui 与 Claude Code 和 Claude Code 动画实装指南。Claude Code 本身的用法请参考 Anthropic 的 Claude Code 文档。
总结
Claude Code 与 Framer Motion 的价值,不只是更快写出漂亮动画,而是把实现、无障碍和 review 条件放进同一个工作流。Masa 实测后最有效的一点,是在第一条提示词中写明 motion/react、Reduced Motion 和手动验证要求。比起事后修补,初始差分就带约束更省时间。想把提示词模板保存下来,可以领取免费速查表;如果要把多个页面一起做动效和 UI 审查,可以从培训与咨询开始。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
Claude Code权限安全阶梯:逐步放开访问而不失控
从只读到有限编辑、验证命令和部署检查的 Claude Code 权限升级流程。
Claude Code 小PR证据包:让小改动真正可审查
用差异、验证命令、公开URL、CTA路径和回滚说明,把Claude Code的小PR变得可审查。
Claude Code 提交前 Review Gate:同时检查差异、测试、公开 URL 和 CTA
提交前用 Claude Code 审查差异范围、build、公开 URL、Gumroad 链接、咨询 CTA、缺少测试和无关文件。