基本概念
twelve-principles は「動き」をデータとして扱います。データなので原則を関数として重ねられ、再生中の姿勢も正確に計算できます。
Pose ─▶ PoseFrame ─▶ MotionSpec ─compile()─▶ WAAPI keyframes ─play()─▶ Element ▲ │ └────┘ anticipate / followThrough / exaggerate / ...1. Pose — 静止状態からの差分
Section titled “1. Pose — 静止状態からの差分”Pose は要素の見た目を、変形していない「静止状態(REST)」を基準に表します。
| キー | 単位 | 静止値 |
|---|---|---|
x y z |
px | 0 |
scale scaleX scaleY |
倍率 | 1 |
rotate rotateX rotateY skewX skewY |
deg | 0 |
opacity |
0〜1 | 1 |
blur |
px | 0 |
elevation |
抽象的な高さ(0〜24 目安) | 0 |
elevation は影に変換されます(Solid Drawing)。ポーズは絶対値です。{ y: -4 } は「今から 4px 上」ではなく「静止位置から 4px 上」を意味します。
2. MotionSpec — 時間軸に並べたポーズ
Section titled “2. MotionSpec — 時間軸に並べたポーズ”interface MotionSpec { frames: PoseFrame[]; // Pose + offset(0..1) + easing duration: number; // ms delay?: number; easing?: Easing; // 全体にかかるイージング iterations?: number; direction?: PlaybackDirection; origin?: string; // transform-origin perspective?: number; // perspective(px)}PoseFrame.easing はそのフレームから次のフレームまでの区間に効きます(WAAPI と同じ規則)。offset を省くと WAAPI と同様に均等配置されます。イージングは CSS 文字列でも JS 関数(スプリングなど)でもよく、関数は compile() 時に 60fps 相当(12〜120 フレーム)のキーフレームへ焼き込まれます。そのため CSS の linear() に対応していないブラウザでもスプリングが動きます。
3. 原則は MotionSpec → MotionSpec の関数
Section titled “3. 原則は MotionSpec → MotionSpec の関数”const base = enter("rise"); // レシピconst wound = anticipate(base); // 2. 予備動作を前置const loose = followThrough(wound); // 5. 終端でオーバーシュートconst loud = exaggerate(loose, 1.4); // 10. 振れ幅を 1.4 倍play(card, loud);どれも入力を変更せず新しい spec を返します。全体イージング付きの spec にフレームを差し込む処理(anticipate / followThrough)は、先に withoutGlobalEasing() でイージングを各区間へ移してから行うため、キーポーズの形が崩れません。
4. 再生 — 1 要素に 1 モーション
Section titled “4. 再生 — 1 要素に 1 モーション”play(el, spec) は Web Animations API で再生し、Animation を返します。
- 置き換え: 同じ要素で新しいモーションを始めると、前のモーションは止まります。
- 割り込み:
currentPose(el)は再生中の姿勢を JS で正確に返します。animateTo(el, pose)はそこから始めるので、途中で方向を変えても飛びません。 - 終了時: 終点が静止状態なら、ライブラリが管理するインラインスタイル(
transformtransform-originopacityfilterbox-shadow)を消し、要素をスタイルシートに返します。それ以外の終点はcommitStyles()でインラインに固定し、アニメーションを解放します。
5. レイヤー — 独立した状態を合成する
Section titled “5. レイヤー — 独立した状態を合成する”ホバーで持ち上がり、押すと潰れ、ポインタに向かって傾く。これらは別々のビヘイビアですが、同じ transform を奪い合います。setLayer(el, name, pose) は名前付きレイヤーを持ち、平行移動・回転・高さは足し算、拡大率・不透明度は掛け算で合成してから遷移します。
setLayer(card, "hover", { y: -3, elevation: 6 });setLayer(card, "press", { scale: 0.97 }); // hover を保ったまま縮むsetLayer(card, "press", null); // press だけ外す6. Personality — 動きの「性格」
Section titled “6. Personality — 動きの「性格」”Personality はテンポ・誇張・弾み・予備動作・潰しの 5 値と、UI ガイドラインの上限を守るかどうかを決める guardrails の組で、レシピとビヘイビアはすべてここから値を読みます。アプリ全体で 1 つの性格を使うことが、12 番目の原則 Appeal の実装です。
| 名前 | tempo | exaggeration | bounce | anticipation | squash | guardrails |
|---|---|---|---|---|---|---|
natural |
1 | 1 | 0.2 | 0.15 | 0.04 | true |
snappy |
0.8 | 0.9 | 0.1 | 0.08 | 0.03 | true |
calm |
1.25 | 0.7 | 0 | 0 | 0.02 | true |
playful |
1 | 1.4 | 0.45 | 0.25 | 0.08 | true |
bouncy |
0.95 | 1.7 | 0.55 | 0.3 | 0.12 | false |
cartoon |
1.15 | 2.4 | 0.65 | 0.45 | 0.22 | false |
既定の guardrails: true では、押下の縮みなどのフィードバックが UI ガイドラインの範囲に収まります。bouncy と cartoon はそれを外した派手な用途向けのプリセットです(派手な動き)。