mcpField
M
motion

motion

github.com/paragonhqデザイン・UI044 回インストール5 週間前に更新
リポジトリ

motion について

インターフェースの動きを選ぶためのデザインスキルで、時間、プロパティ、イージング、アクセシビリティ、パフォーマンスを解説します。アニメーションの必要性を判断し、CSS、Motion、GSAP で実装するプロダクト UI の制作者向けです。

主な機能

  1. 01表示頻度に応じた動きの指針
  2. 02アニメーションの目的と時間のルール
  3. 03CSS、Motion、GSAP の選び方
  4. 04プロパティとイージングの推奨
  5. 05動きを減らす設定への対応指針
  6. 06実装レシピとアンチパターン

活用シーン

  1. 01メニューやポップオーバーの動きの選定
  2. 02ダイアログやトーストのトランジション設定
  3. 03レイアウト変更やタブインジケーターのアニメーション
  4. 04動きを減らす設定に応じた UI アニメーション

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/paragonhq/stealth-skills --skill motion

インストール後、Claude Code で/motionを入力して読み込まれたか確認します。

詳細

motion

Motionは、インターフェース制作で最も目につくため、最も乱用されているツールです。このスキルの目的は、アニメーションを減らし、残すものを適切にすることです。

1行書く前に

次の質問に順番に答えてください。どの段階でもコードを一切書かずにタスクを終えて構いません。それが望ましい結果の場合もあります。

1. どのくらいの頻度で目にしますか?
表示頻度ルール
数秒ごと(入力、リストの移動、行にホバー)何もしない、または120ms未満で、ほとんど気づかれない程度
1日に何度も(メニュー、パレット、タブ、トグル)素早いフィードバックのみ、120〜200ms、楽しさを加えない
1日に数回(ダイアログ、ドロワー、シート、トースト)入場と退場をしっかり表現、180〜320ms
まれ(オンボーディング、成功、初回起動)長く表現豊かなアニメーションを使える唯一の場面

キーボードで開始するアクションは即時に反映します。 ⌘K、Escape、j/k、ショートカットによるタブ切り替えでは、新しい状態を同じフレームで描画します。ここでのアニメーションは遅延に感じられます。

2. 何のためですか?

1つ挙げてください。挙げられないなら、そこで止めてください。

  • 応答 — 押下、切り替え、ドロップへの応答です。インターフェースが操作を認識したことを示します。
  • 位置を示す — 要素がどこから来たか、どこへ移動したかを示します。ポップオーバーはトリガーから展開し、トーストは現れたときと同じ方向に消えます。
  • つなぐ — 2つのレイアウトをつなぎ、コンテンツが突然移動したように見せないようにします。並べ替え、展開、タブインジケーターなどが該当します。
  • 知らせる — 見ていない間に状態が変わったことを示します。保存済みバッジ、カウンターの変化などが該当します。
  • 説明する — 仕組みを見せます。マーケティングとオンボーディングに限ります。

「生き生きと見える」は目的ではありません。「ほかのすべてにアニメーションがある」も目的ではありません。

3. どのツールを使いますか?

目的を果たせる最も簡単な方法を、次の順に選びます。

必要なもの使用するもの
ホバー、フォーカス、押下、色、単純な表示CSS transition
ループまたは複数ステップで、中断が不要CSS @keyframes
マウントされたReact要素の入場と退場、レイアウト変更、スプリング、中断Motion(motion/react)
タイムライン、スクラブ、ピン留め、テキスト分割、シーンの連続表示GSAP
スクロール連動まずCSSのスクロール駆動アニメーションを使い、対応できない場合はScrollTrigger

フェードのためにライブラリをインストールしないでください。プロジェクトにMotionのAnimatePresenceがあるのに、setTimeoutで退場アニメーションを独自実装しないでください。

4. どのプロパティを使いますか?

transformとopacityです。合成処理されるのはこの2つだけで、ほかのプロパティは確実とは言えません。

目的使うもの使わないもの
表示するopacity 0→1 + scale .96→1またはtranslateY 6px→0scale(0)、displayの切り替えのみ
コンテンツに合わせて展開するoverflow: hiddenを設定してheightを計測・アニメーションする、またはgrid-template-rows: 0fr→1frmax-height: 9999pxのアニメーション
端に沿って表示するclip-path: inset(...)width
ぼかしながら表示するfilter: blur(6px→0)をopacityと組み合わせ、短時間で使うぼかしのみ、大きな領域へのぼかし
位置を移動するレイアウトアニメーションでtransformを使う(Motionのlayout、GSAPのFLIP)top/leftのアニメーション
色を変えるtransitionを使ったbackground-color、color、border-colorfilter: hue-rotate

transition: allは絶対に使わないでください。プロパティを列挙します。そうすることで速くなり、予期しない動作を防げます。

5. どのイージングを使いますか?
状況イージング
画面に入る強いイーズアウト:cubic-bezier(0.16, 1, 0.3, 1)
画面から出るここではイーズインを使っても構いません。または同じイーズアウトを再生時間の70%で使います
画面内で移動する(並べ替え、タブインジケーター、サイズ変更)イーズインアウト:cubic-bezier(0.76, 0, 0.24, 1)
ホバーと色easeまたはソフトなイーズアウト。expoは使いません
連続動作(マーキー、スピナー、進捗表示)linear
ジェスチャーやポインターに追従するカーブではなくスプリング
iOSスタイルのシートやドロワーcubic-bezier(0.32, 0.72, 0, 1)

組み込みのease-in-outとease-outは、意図的に調整したように感じさせるには弱すぎます。上記のカスタムカーブを使い、トークンとして保存してください:--ease-out-expo、--ease-in-out-quart。

入場時にイーズインを使わないでください。ユーザーが最も注目するのは最初の50msです。イーズインはその時間を無駄にします。

6. どのくらいの長さにしますか?
要素時間
押下時のフィードバック100–140ms
ホバー状態120–180ms
ツールチップ、小さなポップオーバー140–200ms
メニュー、ドロップダウン、セレクト160–220ms
タブインジケーター、トグルのつまみ180–260ms
ダイアログ、シート、ドロワー220–320ms
トーストの表示240–320ms、非表示は180ms
ページまたはビューの遷移300–450ms
マーケティング向けの表示演出600–1200ms、1回のみ

退場時の時間は、入場時の約70%にします。100ms未満では見えず、プロダクトUIで400msを超えると待たされているように感じます。迷ったら、20%短くしてください。

7. スプリング

ユーザーがドラッグしたり、投げたりする要素や、物理的な感触が必要な要素には、次を使います。

感触モーション設定
キビキビしたUI(インジケーター、チップ、小さな動き){ type: "spring", stiffness: 520, damping: 38, mass: 0.7 }
ソフト(カード、パネル、リストの並べ替え){ type: "spring", stiffness: 260, damping: 28, mass: 0.9 }
ポインターに追従(滑らかに動くホバーハイライト){ type: "spring", stiffness: 650, damping: 45, mass: 0.5 }
リリース後にシートを静止させる{ type: "spring", stiffness: 300, damping: 30 } または bounce: 0 と duration: 0.4

プロダクトUIでバウンスが適切なことはほとんどありません。スプリングのオーバーシュートは、せいぜい1〜2ピクセルに抑えます。揺れる場合は、減衰を大きくしてください。

8. どこから表示しますか?

transform-origin はトリガーに設定します。右上のボタンから開くポップオーバーは、右上の角から広がります。ダイアログは例外で、中央に配置し、中央を基点に拡大します。トーストは、消えるときに向かう側の端から表示します。

9. 時差表示

一度だけ、同時に表示されるリストに限って時差をつけます。項目間は12〜25msとし、上限を設けます。8項目目以降は遅延を追加しません。そうしないと、ユーザーが読み始めた後に残りの項目が表示されます。再レンダリング時やフィルター変更時には、時差をつけないでください。

10. 動きの中断

メニューが開いている途中でユーザーが閉じた場合、開ききってから閉じるのではなく、その時点から反転して閉じる必要があります。CSSトランジションでは、これがデフォルトで行われます。キーフレームでは行われません。Motionでは行われます。中断可能な動きにキーフレームを使っているなら、そのツールの選択は誤りです。

11. 退場

退場時の動きは意図的に、短く、シンプルにし、多くの場合は不透明度の変化だけにします。ダイアログは、表示時にフェードと拡大を行っても、退場時はフェードだけでかまいません。ユーザーが閉じた後、消え始めるまでの1フレームであっても表示が残ってはいけません。クリック後の最初のフレームから、すでに変化している必要があります。

12. モーションの低減

同じ変更で対応を組み込みます。

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

Motionでは、useReducedMotion() を使い、トランスフォームを不透明度に置き換えます。スムーズスクロールのライブラリは完全に無効にします。アニメーションで表示されるはずだった要素は、すべてそのまま表示します。

レシピ

メニュー/ポップオーバー
.menu {
  transform-origin: var(--origin, top left);
  transition: opacity 160ms var(--ease-out-expo), transform 160ms var(--ease-out-expo);
}
.menu[data-state="closed"] { opacity: 0; transform: scale(0.96) translateY(-2px); }
.menu[data-state="open"] { opacity: 1; transform: none; }
ダイアログ
<motion.div
  initial={{ opacity: 0, scale: 0.97, y: 6 }}
  animate={{ opacity: 1, scale: 1, y: 0 }}
  exit={{ opacity: 0, transition: { duration: 0.15 } }}
  transition={{ duration: 0.26, ease: [0.16, 1, 0.3, 1] }}
/>

背景は別にフェードさせます。表示時は少し長めに、退場時は短めにします。

トースト

配置されている側の端から、イーズアウトで約280msかけて表示します。古いトーストは scale(0.95) と translateY(-8px) で縮小して暗くし、新しいトーストの背後に重ねます。同じ側の端に向けて、約180msで退場させます。ホバー中とタブが非表示の間は、タイマーを一時停止します。

コンテンツに合わせた高さ
.grid { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 240ms var(--ease-in-out-quart); }
.grid[data-open] { grid-template-rows: 1fr; }
.grid > * { overflow: hidden; min-height: 0; }
タブインジケーター

layoutId を持つ1つの要素(Motion)をタブ間でスプリング移動させます。各タブの left/width はアニメーションさせないでください。

行間を滑らかに移動するホバーハイライト

絶対配置したピルを1つ使い、pointerenter で位置を更新し、「ポインターに追従」するスプリングでアニメーションさせます。pointerleave では、150msのフェードで取り除きます。

数値の変化

数値全体をフェードさせるのではなく、数字が回転するようにアニメーションさせます。自分で実装するのではなく、数字の列に対応したライブラリ(NumberFlow)を使います。

テキストの表示(マーケティング向けのみ)

単語に分割し、初回表示時に1回だけ、各単語を opacity 0→1、y 6→0、filter blur(6px→0) でアニメーションさせます。単語間には30msの間隔を設けます。

アンチパターン

生成された UI で見られる例代わりに使うもの
transition: all 0.3s easeプロパティを個別に指定し、160~220ms のカスタム ease-out を使います
アイコンに animate-bounce を使う何もしません
すべてのカードがホバー時に影を付けて -4px 浮き上がる次のいずれか 1 つにします:境界線を明るくする、背景を 1 段階明るくする
1.5s のスケルトンシマー静的なスケルトン、またはゆっくりした 2.5s のパルス
ページ読み込み時にすべての要素を表示最大でもタイトルと 1 つの領域だけを、一度表示します
あらゆる場面でスピナーを使うコンテンツにはスケルトン、アクションにのみスピナーを使います
ドロップダウンに duration: 0.5 を使う0.18
プロダクト UI での視差効果使いません

パフォーマンス

will-change: transform は、まもなくアニメーションする要素にのみ適用し、アニメーション後に削除します。backdrop-filter を使う要素のアニメーションは避けてください。大きなぼかしは負荷が高いため、半径は 8px 未満にし、適用範囲を小さく保ちます。CPU にスロットリングをかけてテストし、フレーム落ちが発生する場合は、時間ではなくプロパティを削ってください。

カタログから

コメント