Framer Motion 实战:用迪士尼动画 12 原则打磨 React 界面动效 —— OpenMontage framer-motion 技能全解析
本文以 OpenMontage 仓库中的 .agents/skills/framer-motion/SKILL.md 技能文档为骨架,逐条讲解如何借助 Framer Motion 的声明式 React API 实现迪士尼动画 12 原则(Squash and Stretch、Anticipation、Staging、Follow Through 等)。文中所有代码均可直接复制进
motion组件运行,并补充了关键属性(times、ease、spring阻尼、变体传播等)的语义拆解,帮助前端工程师与动画工程师把"工业级动画规律"落成可维护的 JSX。
技能定位:这份 SKILL.md 在 OpenMontage 中扮演什么角色
OpenMontage 是一个面向智能体(Agent)的视频生产系统,仓库通过三层技能体系让 Agent 完成创意与工程协作。.agents/skills/ 目录存放的是 Layer 3 安装型 Agent 技能,skills/INDEX.md 在"Installed Agent Skills (Layer 3)"表中把 framer-motion 归入 Animation(动画) 类别,与其并列的是 lottie-bodymovin,来源为 pproenca/dot-skills 与 dylantarre/animation-principles。
技能文件的 frontmatter 是 Agent 路由的入口:
name: framer-motion
description: Use when implementing Disney's 12 animation principles with Framer Motion in React applications
这段 description 决定了 LLM/Agent 何时"拿起"这份技能:当需要在 React 应用中使用 Framer Motion 实现迪士尼 12 动画原则时。因此它的适用场景是浏览器侧的 UI 动效,而非视频帧渲染。
理解它在决策链路中的位置,看两个元技能文件:
- skills/meta/animation-runtime-selector.md 的"Animation library decision matrix"中有一行路由规则:"Disney's 12 animation principles for UI motion | framer-motion + Lottie",即UI 动效的迪士尼原则首选 framer-motion 与 Lottie,并指向
.agents/skills/framer-motion与.agents/skills/lottie-bodymovin。 - skills/meta/bespoke-composition.md 强调"到达原则技能,而非成品动画",并指出 framer-motion 所承载的 12 原则是 runtime-agnostic(与运行环境无关)——即使最终渲染走 Remotion,也可以在
spring()/interpolate()代码中复用这些原则本身。
也就是说,这份文档教的是"动效语法 + 表现规律",规律本身可迁移。正文的 12 个示例全部基于 Framer Motion 的 motion 组件与 variants / transition 体系实现。
前提:Framer Motion 声明式 API 的四个抓手
在逐条进入 12 原则之前,先建立四个反复出现的基础概念,所有示例都由它们组合而成:
motion.div等 motion 组件:把普通 DOM 元素升级为可动画元素,animate属性描述目标状态;- 属性值数组(keyframes):
animate={{ x: [0, 100, 200] }}表示一段多关键帧序列,元素依次经过这些值; transition:控制时间、缓动与物理参数(duration、times、ease、type: "spring"、delay);variants:给一组状态命名(如idle、jump),父级可向子树传播状态名。
其中 times 是与关键帧数组一一对应的"时间百分比"数组,例如 duration: 0.5, times: [0, 0.2, 1] 表示前 20% 的时间完成第一步位移动画、余下 80% 完成第二步,配合多姿态组合即可"编舞"。
迪士尼动画 12 原则的逐条 JSX 实现
原文档用 12 个可运行片段覆盖全部原则,本节逐条保留并补充语义解读。
1. Squash and Stretch(挤压与拉伸)
物体在受力瞬间先被压扁/拉长,再恢复,用来表达"弹性"与"体积不变"的错觉:
<motion.div
animate={{ scaleX: [1, 1.2, 1], scaleY: [1, 0.8, 1] }}
transition={{ duration: 0.3, times: [0, 0.5, 1] }}
/>
解读:水平方向 scaleX 先放大到 1.2、竖直方向 scaleY 同步收缩到 0.8,随后双双回到 1。times: [0, 0.5, 1] 保证放大与收缩在时间轴中点对称。压缩与拉伸应近似守恒面积,否则物体会显得"变胖"而非"有弹性"。
2. Anticipation(预备动作)
正式动作前先反向蓄力——起跳前先下蹲——让观众预判下一步:
<motion.div
variants={{
idle: { y: 0, scaleY: 1 },
anticipate: { y: 10, scaleY: 0.9 },
jump: { y: -200 }
}}
initial="idle"
animate={["anticipate", "jump"]}
transition={{ duration: 0.5, times: [0, 0.2, 1] }}
/>
解读:animate 接收变体名数组时会按顺序播放各命名状态;本例先下压 y: 10, scaleY: 0.9(下蹲蓄力)再跃升 y: -200。duration: 0.5 配合 times: [0, 0.2, 1] 让下蹲只占 20% 的时间(约 0.1 秒),留给跳跃更充裕的完成时间。预备动作越夸张、紧随其后的主动作越有爆发感。
3. Staging(表演布局 / 舞台化)
把观众视线导向当前主角:弱化背景、强化主体:
<motion.div animate={{ filter: "blur(3px)", opacity: 0.6 }} /> {/* bg */}
<motion.div animate={{ scale: 1.1, zIndex: 10 }} /> {/* hero */}
解读:背景层被虚化并降透明,主角层放大 1.1 倍并抬升到 zIndex: 10。UI 场景中"谁在说话/谁被点击"就应是谁在"聚光灯"下——弹窗、聚焦卡片、详情展开都是 Staging 的典型落点。
4. Straight Ahead / Pose to Pose(连续动作与姿态到姿态)
传统动画的两条路线:逐帧连续画(Straight Ahead),或先定关键姿态再补中间帧(Pose to Pose)。Framer Motion 的关键帧数组天然是 Pose to Pose:
<motion.div
animate={{
x: [0, 100, 200, 300],
y: [0, -50, 0, -30]
}}
transition={{ duration: 1, ease: "easeInOut" }}
/>
解读:x 定义 4 个水平锚点、y 定义对应的起伏,中间帧完全交给引擎插值。这种方式可预测、易修改——改一个关键帧即可整体重排,适合复杂路径编排。
5. Follow Through and Overlapping Action(跟随动作与重叠动作)
主物体停下后,头发、披风、尾巴等附属物因惯性继续运动并产生"滞后":
<motion.div animate={{ x: 200 }} transition={{ duration: 0.5 }}>
<motion.span
animate={{ x: 200 }}
transition={{ duration: 0.5, delay: 0.05 }} // hair
/>
<motion.span
animate={{ x: 200 }}
transition={{ duration: 0.6, delay: 0.1 }} // cape
/>
</motion.div>
解读:父容器 0.5 秒到达终点;头发延后 0.05 秒起步,披风不仅延后 0.1 秒还用了更长的 0.6 秒行程。结果就是"主体已停、附属仍在滑动",即跟随动作的视觉本质。delay 错峰是重叠动作最廉价的实现方式。
6. Slow In and Slow Out(慢入慢出)
真实物体极少匀速运动:起步慢、收尾慢,中间快:
<motion.div
animate={{ x: 300 }}
transition={{
duration: 0.6,
ease: [0.42, 0, 0.58, 1] // easeInOut cubic-bezier
}}
/>
// Or use: "easeIn", "easeOut", "easeInOut"
解读:ease 支持数组形式的三次贝塞尔曲线 [x1, y1, x2, y2],[0.42, 0, 0.58, 1] 是标准 easeInOut;也可直接用命名缓动 "easeIn" / "easeOut" / "easeInOut"。进入画面用 easeOut、退出用 easeIn 是 UI 动效的基本直觉:物件"驶入减速、驶离加速"。
7. Arc(弧线运动)
自然界几乎没有严格的直线位移——投掷、挥手、跳跃都走弧线:
<motion.div
animate={{
x: [0, 100, 200],
y: [0, -100, 0]
}}
transition={{ duration: 1, ease: "easeInOut" }}
/>
解读:x 匀速推进的同时 y 先抬升到 -100 再回落 0,合成一条抛物线。弧线越"圆润"越显轻快,越"陡峭"越显笨重。在 Framer Motion 中弧线没有专门的 path 指令,用 x/y 关键帧组合即可逼近任意弹道。
8. Secondary Action(次级动作)
用配角动作强化主角情绪——卡片被按下的同时,图标随之轻晃:
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<motion.span
animate={{ rotate: [0, 10, -10, 0] }}
transition={{ duration: 0.3 }}
>
Icon
</motion.span>
</motion.button>
解读:whileHover / whileTap 是 Framer Motion 的手势状态,悬停放大 1.05、按下收缩 0.95;内层图标做 ±10° 的往复摆动作为"回应"。次级动作不能喧宾夺主,幅度应小于主动作。
9. Timing(节奏 / 时间控制)
动画重量由时长说话——越快越轻巧,越慢越沉稳。把节奏抽成常量统一维护:
const timings = {
fast: { duration: 0.15 },
normal: { duration: 0.3 },
slow: { duration: 0.6 },
spring: { type: "spring", stiffness: 300, damping: 20 }
};
解读:0.15 秒适合微反馈(hover、涟漪),0.3 秒适合常见状态切换,0.6 秒适合强调性入场;spring 则由物理参数驱动:stiffness(刚度,越大越硬越弹)与 damping(阻尼,越大越早停)。整套节奏表保证全局动效"语气一致"。
10. Exaggeration(夸张化)
把幅度推到超过真实物理,换取戏剧性与辨识度:
<motion.div
animate={{ scale: 1.5, rotate: 720 }}
transition={{
type: "spring",
stiffness: 200,
damping: 10 // low damping = overshoot
}}
/>
解读:放大 1.5 倍并旋转两整圈(720°),配合弹簧 stiffness: 200, damping: 10——低阻尼会让动画过冲(overshoot),越过目标值再弹回,比线性到达更有"冲劲"。弹簧过冲是最廉价的夸张手段。
11. Solid Drawing(扎实的形体感)
让二维元素具有三维体积与透视,需要真实的纵深参数:
<motion.div
style={{ perspective: 1000 }}
animate={{ rotateX: 45, rotateY: 30 }}
transition={{ duration: 0.5 }}
/>
解读:父容器 style 上设置 perspective: 1000(视距,越小透视越强烈),再旋转 rotateX / rotateY,元素即呈现近大远小的实体翻转效果。卡片翻转、3D 车票、封面开合都属于此类。
12. Appeal(吸引力 / 亲和力)
动画要"讨喜"——不靠炫技,而靠克制的质感与温度:
<motion.div
whileHover={{
scale: 1.02,
boxShadow: "0 20px 40px rgba(0,0,0,0.2)"
}}
transition={{ duration: 0.3 }}
/>
解读:悬停时仅放大 1.02,同时浮现柔和投影 boxShadow,营造"可被拾起"的物理感。与夸张化形成对照:绝大多数常规交互只需 2% 的位移与合适的投影层级,过度动画反而稀释品质。
编排子元素:Stagger Children(交错入场)
列表、网格、菜单等同类元素需要"排队式"错峰入场时,变体(variants)会自动沿组件树向下传播:
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: { staggerChildren: 0.1 }
}
};
<motion.ul variants={container} initial="hidden" animate="show">
{items.map(item => <motion.li variants={itemVariant} />)}
</motion.ul>
解读:父级 ul 的 show 变体声明 staggerChildren: 0.1,引擎便让每个直接子级延迟 100ms 依次完成自身变体动画。hidden / show 两态从父到子统一传播,是最省代码的列表入场方案——注意每个 motion.li 也要声明自己的 variants,否则没有可播放的内容。在 UI 动效里,"交错"正是迪士尼原则中 Timing 与 Follow Through 的组合体现。
必须掌握的关键 API 清单
原文档收尾给出一份浓缩清单,逐项展开如下:
| API | 作用 | 用法要点 |
|---|---|---|
animate |
声明目标状态 | 可传对象(单状态)或数组(关键帧序列),如 animate={{ x: [0, 100] }} |
variants |
命名动画状态 | 配合 initial / animate 传入状态名;状态名沿子树自动传播,可做 staggerChildren |
whileHover / whileTap |
手势动画 | 鼠标悬停 / 按下即触发,适合反馈型动效 |
transition |
时间与缓动 | duration、times、ease、delay;type: "spring" 时用 stiffness / damping |
AnimatePresence |
退出动画 | 包裹条件渲染的子树,让元素"退场"时先播完离开动画再卸载 |
useAnimation |
程序化控制 | 在事件回调 / 定时器里主动 start() / stop() 一组动画,而非依赖声明式状态 |
layout |
布局自动动画 | 元素布局属性变化(尺寸 / 位置)时自动补间过渡,即 FLIP 思路的封装 |
结合技能描述,这套 API 的分工是:variants 负责"编舞与命名",transition 负责"节奏与质感",while* 负责"交互反馈",AnimatePresence 负责"生命周期收尾"。
落地建议:把 12 原则接入 OpenMontage 的动效工作流
在 OpenMontage 中执行 UI / 动效类任务时,遵循 skills/meta/animation-runtime-selector.md 的选型纪律:
- 先定位运行时:本技能解决的是 React UI 动效层(对应文档 frontmatter 中 "in React applications")。当任务是浏览器端产品页、界面演示的动效时,按决策矩阵路由到
framer-motion+lottie-bodymovin;当任务是视频合成(Remotion / HyperFrames / FFmpeg)时,动画原则依然适用,但落地 API 换成 .agents/skills/remotion/SKILL.md 的spring()/interpolate()。 - 坚持"原则优先,成品后置":如 skills/meta/bespoke-composition.md 所述,把 12 原则当作动效语法而非现成模板——每个场景先回答"这帧用哪条原则表达什么情绪",再写
variants。 - 控制节奏变量:把第 9 条的
timings常量表提升为全局 design-token,配合whileHover/whileTap保持交互一致;弹簧默认用stiffness: 300, damping: 20这类"不过冲不拖沓"的中性值,需要戏剧感时才调低阻尼。 - 为 Agent 保留检索入口:技能文件本身即是 Agent 的"说明书",其 frontmatter
description就是触发条件。仓库中 Claude Code 通过.claude/skills/下的符号链接访问这些技能,新增或更新技能后保持两处同步即可。
核心结论一句话:迪士尼 12 原则是"动画为什么好看"的答案,Framer Motion 是"如何在 React 里优雅写出答案"的语法;把 12 段示例代码并入自己的组件库,UI 动效立刻具备可解释、可复用、可评审的工程形态。 本文涉及的原始技能文档位于 .agents/skills/framer-motion/SKILL.md,配套参考 .agents/skills/lottie-bodymovin/SKILL.md 与 skills/INDEX.md 的 Animation 技能清单。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00