春のアニメーション: 自然な UI モーションの物理学
UI デザインにおけるスプリング アニメーションがどのように機能するかを学びます。剛性、減衰、質量、速度を理解します。 React および SwiftUI 用の自然な物理ベースのアニメーションを構築します。
核心
スプリング アニメーションは、質量 - スプリング - ダンパー システムの物理的動作を使用して UI の動きをモデル化します。このシステムでは、コンポーネントの動きは、剛性 (静止方向にどれだけ強く引っ張られるか)、減衰 (エネルギーを失う速さ)、質量 (どれだけの慣性が伝わるか) によって制御されます。期間ベースのアニメーションとは異なり、スプリングはユーザーの速度に自動的に反応し、視覚的な不連続性を引き起こすことなくいつでも中断できます。
持続曲線よりもスプリングがより自然に感じられる理由
物理世界には、「イーズインアウト」や「キュービックベジェ(0.4, 0, 0.2, 1)」のような概念はありません。物理的オブジェクトは力によって加速し、摩擦によって減速します。引き出しがスライドして開くとき、勢いがつきます。手を放すと、摩擦によって静止します。 Spring の物理学はこの物理的現実をソフトウェアで再現します。そのため、iOS UIKit、Android Motion Compose、ExodeUI はすべて Spring-First アニメーション システムです。
スプリングパラメータの説明
それぞれのパラメータを理解することで、思いどおりの物理的な感触をデザインすることができます。
剛性(張力)
スプリングが静止値に向けて引っ張られる強さ。高い剛性 (600+) により、ボタンを押したときのフィードバックのような、きびきびとした応答性の高い UI が作成されます。低い剛性 (100 ~ 200) は、ページ遷移や周囲の浮遊要素に適した、ゆっくりとした浮遊モーションを作成します。
ダンピング(摩擦)
スプリングがサイクルごとにどれだけのエネルギーを失うか。臨界減衰では、スプリングは振動することなく静止状態に達します。減衰が不足しているスプリング (摩擦が低い) はオーバーシュートして跳ね返ります。減衰比 / (2 × sqrt(剛性 × 質量)) によって、減衰が不足しているか、限界減衰であるか、または過剰減衰であるかが決まります。
質量
アニメーション要素が持つ慣性の量。質量が大きいとアニメーションが重く感じられ、方向を変えるのに時間がかかります。ほとんどの UI アニメーションは質量を 1.0 に保ち、剛性と減衰を通じて感触を制御します。
初速度
ユーザーが要素をスワイプまたは投げると、そのジェスチャ速度でスプリングを初期化し、ジェスチャとスプリングの定着の間にシームレスな連続性を生み出すことができます。
UI コンポーネント用の実用的な Spring プリセット
これらのプリセットは、最も一般的な UI インタラクション パターンをカバーしています。
- ボタンタップ: 剛性 500、ダンピング 30 — インスタント、キビキビ、最小限のバウンス
- カード拡張: 剛性 280、減衰 24、質量 1.2 — スムーズな加重拡張
- 引き出し/シート: 剛性 340、減衰 28 - 決定的、わずかなヘタリあり
- 弾むような通知: 剛性 400、減衰 18 — 遊び心のあるオーバーシュート
- ページ遷移: 剛性 220、減衰 22、質量 1.4 — 映画のような、加重
実装コード例
// Spring presets in React with the Motion library (motion.dev).
// The same stiffness / damping / mass values can be saved as motion tokens.
import { motion } from 'motion/react';
const springs = {
snappy: { type: 'spring', stiffness: 500, damping: 30, mass: 1 },
smooth: { type: 'spring', stiffness: 280, damping: 24, mass: 1.2 },
bouncy: { type: 'spring', stiffness: 400, damping: 18, mass: 1 },
cinematic: { type: 'spring', stiffness: 220, damping: 22, mass: 1.4 },
};
export function PhysicsCard({ open }) {
return (
<motion.div
animate={{ y: open ? 0 : 24, scale: open ? 1 : 0.96 }}
transition={springs.smooth}
>
Smooth physics-based card reveal
</motion.div>
);
}