ダブルスプリング:自然なEase-In-Out
•ムン・デスン
springanimationeasing
シングルスプリングの限界
一般的なスプリングアニメーションを考えてみてください。バネに繋がれた物体が目標地点に向かって動きます。
力 = -stiffness × (現在位置 - 目標)
問題は開始の瞬間です。t=0のとき、現在位置と目標の距離が最大になるため、力も最大になります。物体は最初から急激に加速します。
開始 ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━● 目標
↑
距離が最大 = 力が最大 = 急発進!
これがスプリングアニメーションが「ease-out」の感覚しか与えない理由です。速く始まって、ゆっくり終わります。
ダブルスプリングの解決策
ダブルスプリングは2つのスプリングを連結します:
目標 ←―[リーダースプリング]―← リーダー ←―[フォロワースプリング]―← 自分(出力)
- リーダー:目標を直接追跡
- フォロワー(自分):リーダーを追跡(これが実際の出力値)
時間による動作
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)
ダブルスプリングはこの応答を**2回適用(畳み込み)**したものと同等です。結果として、より高次の多項式が掛けられた指数減衰となり、S曲線の特性を生み出します。
物理的には、2つの質量-バネシステムを直列に接続したものと同じです。最初のバネが外部の力を吸収し、2番目のバネは緩和された力だけを受けます。