SSGOI LogoSSGOI
← 返回博客
双弹簧:自然的Ease-In-Out

双弹簧:自然的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曲线特性。

从物理上讲,这就像将两个质量-弹簧系统串联连接。第一个弹簧吸收外力,第二个弹簧只接收缓冲后的力。