双弹簧:自然的Ease-In-Out
•文大承
springanimationeasing
单弹簧的局限性
想象一下典型的弹簧动画。一个连接在弹簧上的物体向目标点移动。
力 = -stiffness × (当前位置 - 目标)
问题出在开始的瞬间。在t=0时,当前位置和目标之间的距离最大,因此力也最大。物体从一开始就急剧加速。
起点 ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━● 目标
↑
距离最大 = 力最大 = 急启动!
这就是为什么弹簧动画只能给出"ease-out"的感觉。它们开始快,结束慢。
双弹簧的解决方案
双弹簧连接两个弹簧:
目标 ←―[领导弹簧]―← 领导者 ←―[跟随弹簧]―← 我(输出)
- 领导者:直接追踪目标
- 跟随者(我):追踪领导者(这是实际输出值)
随时间的行为
t=0(开始)
目标 领导者 我
●━━━━━━━━━━●━━━━━━●
↑
领导者先出发
我和领导者的距离 = 小
→ 作用在我身上的力 = 小
→ 慢慢启动!(ease-in)
t=中间
目标 领导者 我
●━━━━━━●━━━━━━━━━━━━━━━━●
←――――――――――――→
距离拉大
→ 力变大
→ 加速!
t=结束
目标/领导者 我
●━━━━━━━━━━━━━━━━━━━━●
↑
领导者先到达并减速
我和领导者的距离 = 再次缩小
→ 我也减速(ease-out)
通过比喻理解
单弹簧 = 用橡皮筋直接连接到目标
- 一开始橡皮筋拉伸到最大,猛地被拉过去
双弹簧 = 前面有个向导,我跟着向导走
- 即使向导先出发,我和向导之间的距离一开始很小
- 随着向导领先,我逐渐被拉动
- 当向导在终点附近减速时,我也自然减速
用代码来看
// 每帧:
// 第1步:领导者追踪目标
leader = stepSpring(leader, target, constants)
// 第2步:我(跟随者)追踪领导者(不是目标!)
follower = stepSpring(follower, leader.position, constants)
// follower.position 是实际输出值
关键在于从跟随者的角度来看,目标是一个"移动的目标(领导者)"。因为领导者开始时慢慢出发,我也慢慢开始。当领导者在中间加速时,我也加速。当领导者在最后减速时,我也减速。
在SSGOI中使用
// 基本双弹簧(相同的stiffness)
{
stiffness: 180,
damping: 22,
doubleSpring: true
}
// 强ease-in(跟随者stiffness减半)
{
stiffness: 180,
damping: 22,
doubleSpring: 0.5
}
// 更强的ease-in
{
stiffness: 180,
damping: 22,
doubleSpring: 0.3
}
doubleSpring值越小,跟随者的stiffness越低,跟踪领导者的速度越慢。这会产生更强的ease-in效果。
什么时候应该使用?
| 情况 | 推荐 |
|---|---|
| 快速响应很重要 | 单弹簧 |
| 平滑的开始/结束很重要 | 双弹簧 |
| 高级UI转场 | 双弹簧 |
| 游戏角色移动 | 单弹簧 |
在页面转场或模态动画等需要**"设计感"**的地方使用双弹簧效果更好。
数学背景
单弹簧的响应是指数衰减:
x(t) = 1 - (1 + γt)e^(-γt)
双弹簧相当于将这个响应应用两次(卷积)。结果是一个乘以更高阶多项式的指数衰减,产生S曲线特性。
从物理上讲,这就像将两个质量-弹簧系统串联连接。第一个弹簧吸收外力,第二个弹簧只接收缓冲后的力。