motion は何をしますか?
このスキルはモーションを構築します。product-ui/references/values.md のモーションセクションを拡張します。このファイルには確定した数値(イージングカーブ、プレススケール、バウンス、スタガー、持続時間テーブル、トーストとレイアウトの持続時間、移動距離、終了と開始の比率)が含まれています。これらの数値はここでは繰り返しません。このスキルは、決定の順序、トークン、レシピ、失敗モードを追加します。
決定を行う前に、product-ui の §1(テイストジャーナルと優先順位)と §5(どのスキルが何を担当するか)を適用してください。
決定順序
以下を上から下へ進めてください。各ステップでジョブを終了できます。アニメーションがゼロ行という結果も有効であり、その旨を明示してください。
1. 動くべきか?:頻度ゲート
1人のユーザーがこれを1日に何回見るかを尋ねてください。階層はvalues.mdにあります。実際には:
- キーボードによって開始される、または1日に100回以上表示される:モーションなし。理由:繰り返されるモーションは遅延に変わります。
- 1日に数十回表示される(ホバー、タブ切り替え、行選択):なし、または瞬間的な色の変更。
- ユーザーが読んだり操作したりしているデータ(テーブル内の数値、ライブダッシュボードのチャート)は、スタイルのために動くことはありません。理由:モーションは読んでいるものから目をそらします。
- ゲートが「いいえ」と判断した場合、アニメーションを記述しないでください。該当する階層を示し、静的な代替案(瞬間的な状態変更)を提案してください。
2. 目的を命名する
1つの単語を選んでください:spatial(どこから来たか、どこへ行ったか)、state(これが変わった)、feedback(聞きました)、continuity(ジャンプを防ぐ)、explanation(まれな瞬間のみ)。名前を付けられない場合は停止してください。理由:頻繁な要素に対する「見た目が良い」という理由は、UIが遅く感じられる主な原因です。
3. ツールを選ぶ:最も安価で動作するもの
| ニーズ | ツール |
|---|---|
| クラス、属性、ホバー、プレスで切り替わる状態 | CSS transition |
display: none、ポップオーバー、ダイアログから出現 | @starting-style + transition-behavior: allow-discrete |
| 1回限りのシーケンス、スタガー、またはページがビジーなときに実行する必要があるモーション | CSS @keyframes |
JSがタイミングを決定し、キャンセル、逆再生、finished、FLIPが必要 | WAAPI el.animate() |
| スプリング、ベロシティハンドオフ、アンマウントするReactノードの終了、共有レイアウト | Motion (motion/react) |
対象がコンポーネント全体(メニュー、トースト、ドロワー)の場合は、フォーカスハンドリングを備えたライブラリを使用し、pick-library に問い合わせてください。フェードのためにMotionをインストールしないでください。理由:CSSはメインスレッド外で実行され、バンドルサイズが増えません。
4. プロパティ
transformとopacityをアニメーション化してください。filterは ≤ 2pxのスワップブラーの場合のみ使用します。すべてのプロパティを指定してください。transition: allは絶対に使用しないでください。- 高さとレイアウトの変更:
references/recipes.md(グリッド行、FLIP)を参照してください。何かを動かすためにwidth、top、left、marginをアニメーション化しないでください。理由:それぞれがフレームごとにレイアウトを強制します。 box-shadowをアニメーション化しないでください。代わりに疑似要素のopacityをフェードしてください。translateのパーセンテージは要素自身のサイズを基準とするため、translateY(100%)は任意の高さのドロワーやトーストに適合します。
5. カーブと持続時間、使用法に合わせて
推測に最も近い数字を決して選ばないでください。使用法(「メニュークローズ」「アイコンスワップ」)を指定し、references/tokens.md から該当する行を取得してください。使用法に一致する行がない場合は、使用法にちなんで新しいトークンを追加してください。既存のトークンを曲げないでください。理由:トークンはモーションの目的を表すものであり、間違った使用法で近い数値を使用することは間違ったトークンです。
- 開始または終了:
--ease-out。画面上で移動:--ease-in-out。ドロワー:--ease-drawer。色:ease。スピナー:linear。ease-inは絶対に使用しないでください。 - ドラッグ可能または途中で割り込み可能なものは、カーブではなくスプリングを使用してください。理由:カーブはユーザーの手が持っていた速度を受け入れられません。
- 遅く感じる場合:まず持続時間を短くし、次に距離を短くし、最後にカーブに触れてください。
6. 割り込み
- ユーザーが1秒間に2回トリガーできるものは、トランジションまたはスプリングにしてください。
@keyframesは絶対に使用しないでください。理由:トランジションは現在の値から再ターゲットします。キーフレームは再起動してジャンプします。 - WAAPI:現在の値を読み取った後、古いアニメーションをキャンセルし、その値から新しいアニメーションを開始してください(
references/recipes.md)。 - 10回連続でトグルされた場合、正しい状態で終了し、ジャンプがないことを確認してください。テストしてください。
7. 終了
- デフォルトの終了はフェードで、開始よりも短くします(トークンファイルに各ペアがあります)。
- 方向のあるもの(ドロワー、シート、スワイプされたトースト)は、来た道に戻ります。
- 終了する要素はすぐにスペースを占有しなくなるため、フェード中に兄弟要素がそのギャップを埋めます。
- 終了は同時であり、スタガーされることはありません。理由:ユーザーはすでに去ることを決定しています。
8. アニメーションと共に出荷される縮小モーション
不透明度と色は残ります。スライド、スケール、ブラー、バウンスは削除されます。スライドはフェードに、バウンスは 0 になります。モーションを唯一の手がかりとして使用しないでください。パターンは references/recipes.md にあります。
コレオグラフィ
タイミングを設定する前に、動くすべての要素に役割を割り当ててください:
| 役割 | 処理 |
|---|---|
| 主役(注目すべき1つのもの) | 最初に動き、最も多く移動する |
| サポート | 遅れて動き、移動距離は少ない |
| 装飾 | その場でフェードイン、または動かない |
| 静的(コンテナ、クローム) | 決して動かない |
- コンテナごとに1つの開始アニメーション。パネルがスライドインする場合、そのコンテンツはすでにそこにあります。理由:2つの重なった開始アニメーションは、ユーザーを2回待たせます。
- DOM順ではなく、重要度順に並べてください。遅延はvalues.mdのステップ(アイテム間40ms、タイトル、テキスト、アクション間100ms)に従ってください。遅延ではなく、役割によって距離と持続時間を変えてください。
- リストのスタガーはインデックス6(最大遅延240ms)で打ち切ってください。それ以降のアイテムはその遅延を共有します。スタガー中も入力は機能します。
- メニュー、コマンドパレット、タブコンテンツ、または繰り返し開くものをスタガーしないでください。
- 静止を保つ:ユーザーがクリックしようとしているもの、ページが再度読み込まれたときにすでに画面にあるもの、最後の更新から150ms以内に到着した更新(最終状態にジャンプ)。
- 方向は意味に従います:次は右から入り、戻るは左から入ります。ユーザーがトリガーしたものはトリガーから外側に向かって成長します。
必要なときにロード
- references/tokens.md:正確な持続時間、距離、スケール、ブラー、イージング、スプリング値、および既存のコードベースのトークン化のためにロードします。
- references/recipes.md:メニュー、ポップオーバー、ダイアログ、ドロワー、トースト、リストの開始、アイコンスワップ、高さ変更、タブインジケーター、数値変更を構築するときにロードします。
- references/gestures.md:ドラッグ、スワイプ、フリック、シート、またはポインターに従うもののためにロードします。
- references/performance.md:モーションがフレームをドロップしたとき、
will-changeに手を伸ばす前、および更新が高速に到着するときにロードします。
ペア
| 問題あり | 修正済み | 理由 |
|---|---|---|
.menu { transition: all 300ms ease-in } | transition: opacity 200ms, transform 200ms var(--ease-out) | 名前付きプロパティ。ease-inは鈍い。メニューは ≤ 200ms |
scale(0) から scale(1) | scale(0.95) と opacity: 0 から 1 | 何もないところからは何も現れない |
| ポップオーバーが中心からスケール | transform-origin: var(--transform-origin) | トリガーから外側に成長する必要がある |
.open { animation: slide-in 300ms } | トグルされた状態に transition: transform | キーフレームは割り込み時にスナップする |
すべての行 animation-delay: i * 120ms | min(i, 6) * 40ms | 最後の行が300ms以内に到着する |
| ドロワーがスライドイン後にフェードアウト | 来た道をスライドバックアウトする | 方向が説明である |
固定された ease-out でのドラッグ解放 | 解放速度で初期化されたスプリング | 手の勢いが持続する |
pointermove 内の setY(y) | el.style.transform = ... | フレームごとの再レンダリングなし |
終了前に
変更差分を検索し、確認してください。
| 検索対象 | 修正 |
|---|---|
transition: all、* に対する will-change、ease-in | 名前付きプロパティ、削除、--ease-out |
scale(0)、ポップオーバーに対する transform-origin: center | 0.95、トリガーの起点(ダイアログは中央のまま) |
トグルするクラスに対する @keyframes | トランジション |
| tokens.mdの該当行を超える持続時間、またはtokens.mdにない数値 | 使用法のトークン |
keydown ハンドラーまたはパレットに対するモーション | 削除 |
トランジション内の width、height、top、left、box-shadow | transform、opacity、またはレシピ |
prefers-reduced-motion パスがない | 追加する |
実行中のアプリで確認する:4倍速で再生し(DevTools Animationsパネル、または持続時間を乗算)、任意のトグルを連打し、縮小モーションをオンにしてテストし、CPUを4倍にスロットリングしてください。実行できなかったものを述べてください。