动效设计是现代UI/UX中的第四维度——时间。本章将探讨如何通过精心编排的动画创造流畅、自然且富有意义的用户体验。我们将从音乐理论的节奏与和声中汲取灵感,深入研究贝塞尔曲线的数学原理,探讨物理动画的真实感,并掌握性能优化的技术要点。通过本章学习,你将理解动效不仅是装饰,而是传达状态变化、引导注意力、增强可用性的关键设计语言。
在传统的视觉设计中,我们处理的是二维空间(x、y坐标)和深度(z轴)。动效设计引入了第四个维度:时间(t)。这个维度的加入让设计从静态转向动态,从瞬间转向过程。
空间位置: P(x, y, z)
时间函数: P(t) = (x(t), y(t), z(t))
时间维度带来了新的设计变量:
动效的本质是状态的连续变化:
时间感知的心理学基础:
短时感知: < 100ms - 几乎瞬时,用于即时反馈
快速感知: 100-300ms - 快速过渡,不打断思维流
中等感知: 300-1000ms - 明显的动画,用于重要转换
长时感知: > 1000ms - 需要等待,可能引起焦虑
从认知心理学角度,动效服务于以下目的:
1. 自然性原则
真实世界的运动遵循物理定律,用户对这些规律有直觉认知:
牛顿运动定律在UI中的体现:
- 惯性定律:静止物体保持静止,运动物体保持匀速直线运动
UI应用:滚动列表的惯性滑动
- 加速度定律:F = ma,力产生加速度
UI应用:拖拽的阻力感,重量感的模拟
- 作用反作用:每个作用力都有反作用力
UI应用:弹性边界的回弹效果
自然运动的特征:
2. 目的性原则
每个动效都应有明确的功能,避免为动而动:
功能分类:
3. 性能原则
动效不应牺牲性能,流畅度直接影响用户体验:
性能指标:
帧率要求:
- 60 FPS (16.67ms/帧): 理想目标,丝滑体验
- 30 FPS (33.33ms/帧): 最低要求,可接受
- < 30 FPS: 明显卡顿,需要优化
渲染管线:
JavaScript → Style → Layout → Paint → Composite
3ms 3ms 3ms 6ms 2ms = 17ms
优化策略:
4. 一致性原则
动效风格应该在整个产品中保持一致:
一致性维度:
建立动效规范:
动效设计令牌(Motion Tokens):
--duration-instant: 100ms
--duration-fast: 200ms
--duration-normal: 300ms
--duration-slow: 500ms
--easing-standard: cubic-bezier(0.4, 0, 0.2, 1)
--easing-decelerate: cubic-bezier(0, 0, 0.2, 1)
--easing-accelerate: cubic-bezier(0.4, 0, 1, 1)
5. 克制原则
过度的动效会分散注意力,甚至引起视觉疲劳:
克制策略:
判断标准:
必要动效:
✓ 提供操作反馈
✓ 显示状态变化
✓ 保持空间连续性
可选动效:
? 增强品牌感
? 提升愉悦度
? 展示加载过程
避免动效:
✗ 纯装饰用途
✗ 重复循环动画
✗ 影响内容阅读
线性运动是最简单但也最不自然的动画形式:
线性函数: f(t) = t
位置公式: x(t) = x₀ + v·t
速度公式: v(t) = 常数
加速度: a(t) = 0
线性运动图示:
位置 速度 加速度
^ ^ ^
| / |------ |
| / | |------ (0)
| / | |
| / | |
| / | |
|/_______> |_______> |_______>
时间 时间 时间
线性运动的问题:
非线性运动的优势:
缓动函数将归一化时间t∈[0,1]映射到进度值p∈[0,1]:
Ease-In(缓入)
数学表达:
- 二次: f(t) = t²
- 三次: f(t) = t³
- 四次: f(t) = t⁴
- 五次: f(t) = t⁵
特征分析:
- 导数: f'(t) = n·t^(n-1) (速度递增)
- 起始速度: f'(0) = 0 (静止开始)
- 结束速度: f'(1) = n (最大速度)
应用场景:
- 物体离开屏幕(加速离开)
- 下落运动(重力加速)
- 消失动画(快速消失)
- 菜单收起(加速收缩)
Ease-Out(缓出)
数学表达:
- 二次: f(t) = 1 - (1-t)²
- 三次: f(t) = 1 - (1-t)³
- 四次: f(t) = 1 - (1-t)⁴
- 五次: f(t) = 1 - (1-t)⁵
特征分析:
- 导数: f'(t) = n(1-t)^(n-1) (速度递减)
- 起始速度: f'(0) = n (最大速度)
- 结束速度: f'(1) = 0 (静止结束)
应用场景:
- 物体进入屏幕(减速进入)
- 停止运动(缓慢停止)
- 出现动画(优雅出现)
- 菜单展开(减速展开)
Ease-In-Out(缓入缓出)
数学表达:
标准形式: f(t) = t < 0.5 ? 2t² : 1 - 2(1-t)²
通用形式(n次方):
f(t) = t < 0.5
? 2^(n-1) · t^n
: 1 - 2^(n-1) · (1-t)^n
特征分析:
- 对称性: f(t) + f(1-t) = 1
- 中点速度最大: f'(0.5) = max
- 两端速度为零: f'(0) = f'(1) = 0
应用场景:
- 元素位置转换(平滑过渡)
- 页面切换(自然转场)
- 大多数UI动画(通用选择)
- 焦点转移(流畅切换)
Power缓动族
幂函数族提供不同强度的加速效果:
缓动强度对比:
t=0.25 t=0.5 t=0.75
Linear: 0.25 0.50 0.75
Quad: 0.06 0.25 0.56 (t²)
Cubic: 0.02 0.13 0.42 (t³)
Quart: 0.004 0.06 0.32 (t⁴)
Quint: 0.001 0.03 0.24 (t⁵)
选择指南:
三角函数缓动
使用正弦和余弦创造平滑曲线:
Sine缓动:
- Ease-In: f(t) = 1 - cos(t × π/2)
- Ease-Out: f(t) = sin(t × π/2)
- Ease-In-Out: f(t) = 0.5 × (1 - cos(t × π))
特点:
- 比Power函数更平滑
- 导数连续,无突变点
- 自然的波动感
指数缓动
创造极端的加速效果:
Exponential缓动:
- Ease-In: f(t) = 2^(10(t-1))
- Ease-Out: f(t) = 1 - 2^(-10t)
- Ease-In-Out:
f(t) = t < 0.5
? 2^(10(2t-1))/2
: 1 - 2^(10(1-t))/2
特点:
- 极端的速度变化
- 适合强调和爆炸效果
- 谨慎使用,容易过度
三次贝塞尔曲线是CSS和大多数动画库使用的标准,提供了极大的灵活性:
数学基础
参数方程:
B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃
展开形式:
B(t) = P₀(1-3t+3t²-t³) + P₁(3t-6t²+3t³) + P₂(3t²-3t³) + P₃t³
对于动画曲线,固定端点:
P₀ = (0, 0) - 起始点
P₃ = (1, 1) - 结束点
P₁ = (x₁, y₁) - 第一控制点
P₂ = (x₂, y₂) - 第二控制点
几何理解
控制点的作用:
1 | P₃(1,1)
| ╱│
| ╱ │P₂ (拉力点)
| ╱ │
| ╱ │P₁ (推力点)
0 |╱____│_____
0 1
P₁控制起始切线方向和强度
P₂控制结束切线方向和强度
De Casteljau算法
贝塞尔曲线的几何构造方法:
t时刻的点通过递归线性插值得到:
Q₀ = (1-t)P₀ + tP₁
Q₁ = (1-t)P₁ + tP₂
Q₂ = (1-t)P₂ + tP₃
R₀ = (1-t)Q₀ + tQ₁
R₁ = (1-t)Q₁ + tQ₂
B(t) = (1-t)R₀ + tR₁
常用贝塞尔曲线库
Material Design:
- Standard: cubic-bezier(0.4, 0.0, 0.2, 1)
- Decelerate: cubic-bezier(0.0, 0.0, 0.2, 1)
- Accelerate: cubic-bezier(0.4, 0.0, 1, 1)
- Sharp: cubic-bezier(0.4, 0.0, 0.6, 1)
iOS Human Interface:
- ease: cubic-bezier(0.25, 0.1, 0.25, 1)
- ease-in: cubic-bezier(0.42, 0, 1, 1)
- ease-out: cubic-bezier(0, 0, 0.58, 1)
- ease-in-out: cubic-bezier(0.42, 0, 0.58, 1)
自定义效果:
- 弹性: cubic-bezier(0.68, -0.55, 0.265, 1.55)
- 回弹: cubic-bezier(0.175, 0.885, 0.32, 1.275)
- 预备: cubic-bezier(0.6, -0.28, 0.735, 0.045)
控制点约束
CSS中的cubic-bezier函数有特定约束:
有效范围:
x₁, x₂ ∈ [0, 1]
y₁, y₂ ∈ (-∞, +∞)
超调效果:
y > 1: 超过目标值
y < 0: 反向运动
贝塞尔曲线调试技巧
弹性动画模拟弹簧的物理特性,创造自然的振荡效果:
物理模型
弹簧-质量-阻尼系统:
╱╲╱╲╱╲ k (弹簧)
│ │
│ m │ (质量)
│ │
├─┴─┴─┤ c (阻尼器)
运动方程:
mẍ + cẋ + kx = 0
标准形式:
ẍ + 2ζωₙẋ + ωₙ²x = 0
其中:
ωₙ = √(k/m) - 自然频率
ζ = c/(2√(km)) - 阻尼比
解析解
根据阻尼比ζ的不同,系统有三种行为:
1. 欠阻尼 (ζ < 1) - 振荡衰减:
x(t) = Ae^(-ζωₙt) × cos(ωdt + φ)
ωd = ωₙ√(1-ζ²) - 阻尼频率
2. 临界阻尼 (ζ = 1) - 最快收敛:
x(t) = (A + Bt)e^(-ωₙt)
3. 过阻尼 (ζ > 1) - 缓慢收敛:
x(t) = Ae^(r₁t) + Be^(r₂t)
r₁,₂ = -ωₙ(ζ ± √(ζ²-1))
参数影响
阻尼比ζ的效果:
ζ = 0.1: 多次振荡,缓慢衰减
ζ = 0.3: 中等振荡,适中衰减
ζ = 0.5: 少量振荡,快速衰减
ζ = 0.7: 轻微振荡,很快稳定
ζ = 1.0: 无振荡,最快到达
ζ = 1.5: 无振荡,缓慢接近
自然频率ωₙ的效果:
ωₙ↑: 振荡更快,周期更短
ωₙ↓: 振荡更慢,周期更长
实用参数配置
UI动画常用配置:
轻快弹性:
- stiffness: 300
- damping: 20
- mass: 1
- ζ ≈ 0.36
标准弹性:
- stiffness: 100
- damping: 10
- mass: 1
- ζ ≈ 0.5
重型弹性:
- stiffness: 50
- damping: 15
- mass: 2
- ζ ≈ 0.75
RK4数值积分
对于复杂的弹性系统,使用四阶龙格-库塔法:
状态向量: S = [x, v]
导数函数: f(S, t) = [v, -(2ζωₙv + ωₙ²x)]
RK4步骤:
k₁ = h × f(Sₙ, tₙ)
k₂ = h × f(Sₙ + k₁/2, tₙ + h/2)
k₃ = h × f(Sₙ + k₂/2, tₙ + h/2)
k₄ = h × f(Sₙ + k₃, tₙ + h)
Sₙ₊₁ = Sₙ + (k₁ + 2k₂ + 2k₃ + k₄)/6
弹性缓动的变体
1. 弹性进入 (Elastic In):
f(t) = -2^(10(t-1)) × sin((t-1.1)×2π/period)
2. 弹性退出 (Elastic Out):
f(t) = 2^(-10t) × sin((t-0.1)×2π/period) + 1
3. 弹性进出 (Elastic InOut):
前半段使用Elastic In
后半段使用Elastic Out
4. 回弹 (Back):
f(t) = t² × ((s+1)×t - s)
其中s控制回弹幅度,通常s=1.70158
动效设计可以借鉴音乐的节奏概念:
基础节拍单位
全音符 = 1000ms
二分音符 = 500ms
四分音符 = 250ms
八分音符 = 125ms
十六分音符 = 62.5ms
节奏模式
规则节奏: ● ● ● ● (等间隔)
切分节奏: ● · ● · (长短交替)
渐进节奏: ● ·· ··· ···· (逐渐加密)
多个动画元素的协调类似音乐和声:
同步(Unison) 所有元素同时开始和结束:
元素A: ==========>
元素B: ==========>
元素C: ==========>
级联(Cascade) 元素依次开始,创造流动感:
元素A: ==========>
元素B: ==========>
元素C: ==========>
交错(Stagger) 部分重叠的动画序列:
元素A: ==========>
元素B: ==========>
元素C: ==========>
Duration(持续时间)
Delay(延迟) 创造节奏感的关键参数:
延迟公式: delay(n) = n × baseDelay
渐进延迟: delay(n) = baseDelay × easing(n/total)
重复与循环
无限循环: animation-iteration-count: infinite
有限重复: animation-iteration-count: 3
交替方向: animation-direction: alternate
直线路径最简单但往往不够自然:
直线插值: P(t) = P₀ + t(P₁ - P₀)
曲线路径更接近自然运动:
二次贝塞尔路径:
P(t) = (1-t)²P₀ + 2(1-t)tP₁ + t²P₂
SVG提供了强大的路径定义语言:
M = moveto(移动到)
L = lineto(直线到)
C = curveto(三次贝塞尔曲线)
Q = quadratic Bézier curve(二次贝塞尔曲线)
A = elliptical Arc(椭圆弧)
Z = closepath(闭合路径)
路径动画的offset-path属性:
轨迹定义: path('M10 10 C 20 20, 40 20, 50 10')
偏移距离: offset-distance: 0% → 100%
旋转对齐: offset-rotate: auto
抛物线运动
水平位置: x(t) = v₀ₓ × t
垂直位置: y(t) = v₀ᵧ × t - ½g × t²
圆周运动
x(t) = r × cos(ωt + φ)
y(t) = r × sin(ωt + φ)
其中: r = 半径, ω = 角速度, φ = 初相位
螺旋运动
x(t) = r(t) × cos(θ(t))
y(t) = r(t) × sin(θ(t))
r(t) = r₀ + vᵣ × t (线性增长)
θ(t) = ω × t
优先使用GPU加速的属性:
避免触发重排的属性:
创建独立合成层:
will-change: transform;
transform: translateZ(0); /* hack */
合成层的内存成本:
内存 = 宽度 × 高度 × 4字节
例: 1920×1080层 = 8.3MB
关键性能指标:
性能预算:
主线程时间: < 10ms
合成时间: < 3ms
总帧时间: < 16ms
使用RAF同步浏览器刷新:
帧率计算:
targetFPS = 60
frameTime = 1000 / targetFPS = 16.67ms
时间戳差值:
deltaTime = currentTime - lastTime
progress = deltaTime / duration
按钮状态反馈
默认 → 悬停: scale(1.05), 150ms, ease-out
悬停 → 按下: scale(0.95), 100ms, ease-in
按下 → 释放: scale(1), 200ms, elastic
加载状态指示
脉冲效果: opacity 0.5→1→0.5, 1.5s, ease-in-out, infinite
旋转加载: rotate 0→360deg, 1s, linear, infinite
进度条: scaleX 0→1, duration = f(progress)
通知入场动效
滑入: translateY(-100%)→0, 300ms, ease-out
淡入: opacity 0→1, 200ms, ease-in
弹性: scale(0)→1.1→1, 400ms, cubic-bezier
共享元素过渡 保持关键元素的视觉连续性:
1. 记录起始位置和大小
2. 计算目标位置和大小
3. 应用transform动画
4. 同步其他元素变化
视差滚动 不同层级的差速运动:
背景层: translateY = scrollY × 0.5
中景层: translateY = scrollY × 0.7
前景层: translateY = scrollY × 1.0
渐进式展现 内容分批次加载和显示:
标题: delay = 0ms
副标题: delay = 50ms
内容: delay = 100ms
图片: delay = 150ms
设备性能检测
高性能设备: 完整动效,60fps目标
中等设备: 简化动效,降低复杂度
低端设备: 最小动效,仅保留必要反馈
Reduced Motion适配
媒体查询: @media (prefers-reduced-motion: reduce)
策略:
- 移除装饰性动画
- 缩短动画时长
- 使用淡入淡出替代复杂动效
Chrome DevTools
性能标记点
开始标记: performance.mark('animation-start')
结束标记: performance.mark('animation-end')
测量: performance.measure('animation', 'animation-start', 'animation-end')
动效设计是将时间维度融入用户界面的艺术与科学。通过本章学习,我们掌握了:
关键公式回顾:
记住:优秀的动效设计是隐形的——用户感受到的是流畅自然的体验,而非动画本身。
练习7.1:缓动函数理解 给定一个元素需要在500ms内从位置x=0移动到x=200px,分别计算使用linear、ease-in(t²)、ease-out(1-(1-t)²)函数时,在t=250ms时元素的位置。
提示:将时间归一化到[0,1]区间,应用缓动函数,再映射到实际位置。
练习7.2:动画时序设计 设计一个卡片列表的进入动画,有6张卡片需要依次显示。每张卡片动画时长300ms,如何设置延迟使得:
提示:考虑动画重叠和级联效果。
练习7.3:性能优化判断 以下CSS属性变化,哪些会触发重排(reflow),哪些只触发重绘(repaint),哪些可以仅在合成层处理?
提示:考虑浏览器渲染管线的不同阶段。
练习7.4:贝塞尔曲线设计 设计一个”弹跳”效果的贝塞尔曲线,要求:
提示:P1控制起始切线,P2控制结束切线,y值可以超过1。
练习7.5:复合动画编排 设计一个模态框的完整动画序列,包括:
提示:考虑动画的层次关系和用户注意力流向。
练习7.6:物理动画模拟 实现一个”重力球”动画:球从高处落下,碰到地面后反弹,每次反弹高度递减,最终静止。给出:
提示:使用能量守恒和恢复系数。
问题:添加过多装饰性动画,影响可用性 解决:每个动画都应有明确目的,遵循”少即是多”原则
问题:在低端设备上动画卡顿 解决:实施性能预算,提供降级方案,使用will-change谨慎
问题:动画太快用户看不清,太慢显得拖沓 解决:遵循平台规范,A/B测试优化,考虑上下文
问题:不同组件使用不同的动画风格 解决:建立动画设计系统,定义标准缓动函数库
问题:动画对部分用户造成不适 解决:实现prefers-reduced-motion,提供动画开关
问题:所有动画都加入弹性,显得廉价 解决:弹性效果应用于需要强调和活力的元素
问题:用户必须等待动画完成才能操作 解决:支持动画中断,快速连续操作时跳过动画
问题:相同动画用于不同场景 解决:根据触发源、目标、重要性调整动画参数