mcpField
M
motion

motion

github.com/raphaelsalajaデザイン・UI67235 回インストール5 日前に更新
リポジトリ

motion について

motion は、画面全体の UI アニメーション、ジェスチャー、モーションブラーの振る舞いを定めるスキルです。UI の動きを実装する開発者向けに、時間、イージング、動きを減らす設定、実装ルールを詳しく示します。

主な機能

  1. 01時間とイージング曲線の指定
  2. 02UI 状態遷移の指針
  3. 03ジェスチャーと閉じ方の定義
  4. 04@web-kits/motion-blur によるモーションブラー
  5. 05動きを減らす設定への対応

活用シーン

  1. 01ページやパネルの遷移実装
  2. 02ドラッグやスワイプで閉じる UI の構築
  3. 03動く要素へのモーションブラー追加
  4. 04動きを減らす設定へのアニメーション対応確認

インストール

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

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

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

同梱ファイルagents/openai.yamlrecipes.md

詳細

モーション

このスキルでは、あらゆる画面での動きを扱います。アニメーションの対象、曲線と時間、ジェスチャー、終了時の動き、モーションブラー、モーションの軽減について説明します。

時間と曲線は厳密です

プロジェクトにモーショントークンがある場合は、それを使います。ない場合は、ここに記載された時間、曲線、スケールを厳守してください。ユーザーが開始するモーションが300msを超えて続く場合、スタイルを問わず指摘対象です。

モーションブラーの描画には@web-kits/motion-blurを使用します。デフォルト値は厳密に定められており、シャッター角度は180°、Echoは1.5pxごとに最大16コピー、ブラーの上限は1.05です。

アニメーション自体が必要かどうかはrestraintで扱います。既存のアニメーションを分解する場合はdeconstructで扱います。フレームレートを維持できるかどうかはframesで扱います。サーフェス、読み込み、ホバースタイル、サウンドはdefaultsで扱います。

アプリのクロームはフェードさせる

ページ切り替え、パネル、読み込み状態にはopacityを使ってフェードを適用し、スライドやスケールは使いません。これはmotion-opacity-for-chromeです。変化する軸だけをアニメーションさせてください。高さだけが変わるのに幅をアニメーションさせるのはmotion-changing-axis-onlyです。

入場アニメーションは再生しません

ページを切り替えても、入場アニメーションは再生しません。ルートレベルのAnimatePresenceと、状態の切り替えをラップするものにはinitial={false}を設定します。これはmotion-no-replay-on-entryです。キャッシュされたビューは、フェードやスケルトンのちらつきなしにすぐ描画されます。これはmotion-no-flash-on-returnです。recipes.mdを参照してください。

アニメーションさせるプロパティを指定します

トランジションでは、通常opacityやtransformなど、アニメーションさせるプロパティを列挙します。transition: allはmotion-name-the-propertiesです。

切り替わる状態にはトランジションを使い、キーフレームは一度だけ実行します

インタラクティブな状態の変化にはトランジションまたはスプリングを使います。ユーザーが逆方向に操作すると、動きの目標が切り替わります。切り替え可能なクラスに設定されたキーフレームアニメーションはmotion-transitions-for-stateです。キーフレームは一度だけ実行されるシーケンスに使ってください。

モーションは発生元から始めます

ドロップダウンやポップオーバーはトリガーからスケールさせ、transform-originをその端に設定します。メニューが自身の中央から広がる場合はmotion-origin-from-triggerです。モーダルにはトリガーとなる基点がないため、中央にとどまります。

ゼロから拡大させません

入場時はopacity: 0とともに、scale(0.95)以上から始めます。scale(0)はmotion-no-scale-from-zeroです。

ジェスチャーにはスプリングを使います

ドラッグ、スワイプ、移動中にユーザーが反転できるあらゆるモーションにはスプリングを使います。指を離すときは、ポインターの速度をスプリングに渡します。固定のease-outを使ったドラッグ終了はmotion-spring-for-gesturesです。recipes.mdを参照してください。

フリックで閉じ、端では抵抗を加えます

スワイプは距離または速度に応じて閉じます。そのため、速度が約0.11 px per millisecondを超えると、短いフリックでも十分です。速いフリックを無視するシートはmotion-flick-dismissesです。

境界を越えてドラッグすると、壁にぶつかるのではなく、摩擦が強まります。上端で急停止するドロワーはmotion-friction-at-edgesです。ドラッグ開始時にポインターをキャプチャし、追加のタッチは無視します。recipes.mdを参照してください。

すべてのジェスチャーにボタン操作を用意します

ドラッグ、スワイプ、ピンチのそれぞれに、クリックとキーボードでも操作できる手段を用意します。ドラッグ操作だけで並べ替えるリストはmotion-gesture-alternativeです。

変化に合ったイージングを使います

トーストなどのシステム上の変化には、スプリングではなくイージングを使います。入場と終了にはイーズアウトを、ビューの切り替えにはイーズインとイーズアウトの両方を使います。リニアはプログレスバーと時計にのみ使います。ease-inで入場するモーダルはmotion-ease-by-directionです。

組み込みのease-outキーワードは弱すぎます。入場と終了にはcubic-bezier(0.23, 1, 0.32, 1)を使い、画面上の移動にはcubic-bezier(0.77, 0, 0.175, 1)を使います。単独のease-outはmotion-strong-ease-outです。

閉じる動作は開く動作より速くします

閉じる動作は開く動作より短くします。閉じるときやホバー解除時に遅延を加えません。表示にかかった時間と同じだけかけて閉じるモーダルはmotion-close-fasterです。

モーションは300ms未満にします

ホバー時の色変化はトランジションなしで瞬時に切り替わります。ホバー時にボタンの背景を200msかけてフェードさせる動作は、motion-instant-hover-colorに該当します。

押下時やホバー時の移動には120~180msかけます。小さな状態変化には180~260msかけます。ユーザーが開始する動作は、パネル全体やドロワーの最大400msを除き、300msを超えないようにします。ドロップダウンに500msかける動作は、motion-under-300msに該当します。

mode="wait"では、退場と登場が順番に再生されるため、それぞれの時間を半分にします。動きが遅く感じる場合は、イージングを変える前に時間を短くします。サンプルはrecipes.mdにあります。

頻繁に行う操作にはアニメーションを付けません

入力、キーボードショートカット、キーボードによるナビゲーション、高速な切り替えにはアニメーションを付けません。キーボードで開いたコマンドパレットは即座に表示します。コンテキストメニューも即座に表示し、閉じるときはフェードアウトさせてもかまいません。キー入力のたびにスケールを変化させる動作は、motion-no-motion-on-repeatに該当します。

ツールチップの待機時間は最初の一度だけにします

グループ内の最初のツールチップは、遅延後に開きます。いずれかのツールチップを表示している間は、その隣のツールチップは即座に、アニメーションなしで開きます。すべてのツールチップは即座に閉じます。ツールバーの各ツールチップが毎回遅延時間いっぱい待つ動作は、motion-tooltip-delayに該当します。

アイコンの切り替えにはクロスフェードを使います

コピーからコピー済みへの切り替えのように、アイコンをその場で切り替える場合はクロスフェードさせます。スケールは0.25から1、不透明度は0から1、ぼかしは4pxから0に変化させます。即座に切り替える動作は、motion-icon-crossfadeに該当します。recipes.mdを参照してください。

iconsのモーフ形式にある2つのアイコンは、代わりにモーフさせます。移動のイージングを使って、それぞれのパスのdを小さな状態変化としてアニメーションさせます。モーション軽減設定が有効な場合は、クロスフェードさせます。モーフ形式の2つのアイコンをクロスフェードさせる動作は、motion-icon-morphに該当します。recipes.mdを参照してください。

クロスフェードにはぼかしを加えます

2つの状態間のクロスフェードが、それでも2つのオブジェクトのように見える場合は、実行中にfilter: blur(2px)を加えます。Safariでは負荷が高くなるため、ぼかしは20px未満にします。切り替えの途中で2つのラベルが目に見えて重なる動作は、motion-blur-the-crossfadeに該当します。

モーションブラーは目で追えない場合にだけ使います

画面自体が目で追うものをすでにぼかしているため、さらにぼかしを加えると、読んでいるテキストが灰色の帯になってしまいます。投げたカード、閉じたシート、スナップして表示されるパネル、スピナーをぼかします。スクロール、カルーセルのページ、所定の位置にスライドするリスト、読んでいるテキストをぼかす動作は、motion-blur-untracked-onlyに該当します。

ReactではuseMotionBlurを、その他の環境ではmotionBlur(element)を使って残像を描画します。このパッケージは速度、フレーム時間、シャッター速度に応じてサイズを調整し、イージングに追従して、停止時に解除します。動く要素に手書きのfilter: blur()やコピーのループを使う動作は、motion-blur-packageに該当します。recipes.mdを参照してください。

ぼかすのは動く要素です

このパッケージは、ホスト自身のトランスフォームを監視します。子要素がアニメーションしているときにラッパーにrefを設定しても何も描画されず、motion-blur-ref-on-moverに該当します。MotionBlurコンポーネントを使う場合は、コンポーネント自体をアニメーションさせ、その内側にある要素にスタイルを設定します。

ホストの最初の子要素がレイヤーとなり、残像はそのレイヤー内に描画されます。背景、境界線、シャドウはレイヤー自体かその内側に設定します。ホストに設定した背景は残像の下で鮮明なままとなり、motion-blur-transparent-hostに該当します。

この手法ではレイヤーのトランスフォームを書き換えるため、レイヤー自体にはトランスフォームを設定しません。動いている間にレイヤーの傾きやスケールが失われる動作は、motion-blur-bare-layerに該当します。内側の要素に設定してください。

残像の描画にはEchoを使います

デフォルトのecho()を使います。カメラのブラーのような形状を持ち、回転、スケール、イージングに追従する唯一の手法です。framesのトレースでコピーのコストが高すぎるとわかった場合にだけ、squeeze()、svg()、smear()を使います。

この3つはレイヤー全体を一定量、一定方向にぼかしますが、回転する要素の縁はそれぞれ異なる速度で動きます。Echo以外を使ったスピナーやオービットは、motion-blur-echo-for-turnsに該当します。テキストを含む要素で、スミアの上限を1.05より大きくする動作は、motion-blur-smear-limitに該当します。

squeeze()とsvg()はレイヤーとその子要素を反対方向に回転させるため、レイヤーが包む要素は1つだけにします。子要素が2つあるレイヤーはエラーになり、motion-blur-one-childに該当します。

曲線にはパスを渡します

物理演算のループなど、手動で設定した位置では、フレームごとに blur.render(previous, current) を1回呼び出します。動きが曲がる場合は、代わりにパスを渡すと、複製が曲線に沿って配置されます。軌道上の2つの変換には motion-blur-follow-path を使います。recipes.mdを参照してください。

デモを通常の4分の1の速度で再生すると、シャッター値を4倍にする必要があります。そのため、shutter: 720 にすると、通常速度と同じ長さの軌跡を保てます。スローモーションの制御を180°のままにしている場合は motion-blur-slowdown-shutter です。

カスタムテクニックはすべてを元に戻します

カスタムテクニックは、clear で paint が設定したすべてのスタイルを削除し、destroy でページに追加したものをすべて削除するペインターを返します。レンダリング内で作成されたテクニックには key も設定されるため、同等のテクニックではペインターが再構築されません。静止した要素にフィルターが残ったままの場合、または key のないインラインテクニックは motion-blur-custom-technique です。recipes.mdを参照してください。

押下時の拡大縮小は控えめにします

押下されたコントロールは 0.98 に拡大縮小し、つぶしや伸ばしは 0.95–1.05 の範囲に収めます。whileTap={{ scale: 0.8 }} は motion-press-scale です。リスト項目のスタガー間隔はそれぞれ50ms未満にします。リストでの staggerChildren: 0.15 は motion-short-stagger です。タイトル、テキスト、アクションなど、1回だけ再生する段階的な導入では、100ms間隔でスタガーします。通常のインタラクションではスタガーを使わず、スタガーによって入力を妨げることもありません。

終了アニメーションにはプレゼンス境界が必要です

exit を持つ条件付き要素は、安定したIDをキーとして AnimatePresence 内に配置し、initial の値へと終了します。ラッパーがないために終了アニメーションが再生されない場合は motion-exit-in-presence です。recipes.mdを参照してください。

コンポーネントライブラリのダイアログはマウントしたままにし、open プロパティを切り替えます。open && の背後でレンダリングされ、閉じるアニメーションが失われるダイアログは motion-dialogs-stay-mounted です。

サイズは2つ目の要素のコンテンツに合わせます

コンテナの幅または高さをアニメーションさせるには、内側の要素を ResizeObserver で計測し、外側の要素をアニメーションさせます。同じノードを計測してアニメーションさせる場合は motion-measure-inner です。recipes.mdを参照してください。

パネルが折りたたまれている間、コンテンツは開いているときの幅を保つため、アニメーション中に再フローは発生しません。サイドバーが閉じるときに、サイドバーのコンテンツに w-full を設定する場合は motion-hold-content-size です。

動きを減らす設定では移動をなくします

prefers-reduced-motion: reduce に一致する場合、スライド、拡大縮小、ぼかしは不透明度のクロスフェードに切り替えます。この設定を無視するスライド式ドロワーは motion-reduced-motion です。

動きのぼかしは、この設定が有効な場合に軌跡をなくし、reducedMotion は "user" のままにします。reducedMotion: "never" は motion-blur-reduced-motion です。

ペア

すべてのチェックはペアになっています。問題のある行の隣に、それを置き換える行が配置されています。

RuleBrokenReplacement
motion-opacity-for-chromeページ上の initial={{ x: 20, scale: 0.95 }}initial={{ opacity: 0 }}
motion-changing-axis-only高さだけが変わるときの animate={{ width, height }}animate={{ height }}
motion-no-replay-on-entryルートを囲む <AnimatePresence> によって、入場アニメーションが再生される<AnimatePresence initial={false}>
motion-no-flash-on-returnキャッシュ済みのビューでスケルトンが一瞬表示されるキャッシュ済みのビューをすぐにレンダリングする
motion-name-the-propertiestransition: all 200mstransition: opacity 200ms, transform 200ms
motion-transitions-for-state.open { animation: slide-in 200ms }切り替えるプロパティに transition: transform 200ms を指定する
motion-origin-from-triggercenter を起点に拡大するドロップダウン左揃えのトリガーの下に transform-origin: top left を指定する
motion-no-scale-from-zeroポップオーバーの initial={{ scale: 0 }}initial={{ scale: 0.95, opacity: 0 }}
motion-spring-for-gesturesduration: 0.3, ease: "easeOut" でドラッグを離すvelocity: info.velocity.x を指定した type: "spring"
motion-flick-dismisses固定距離を超えた場合のみ閉じる距離、または 1 ミリ秒あたり 0.11 px を超えた場合に閉じる
motion-friction-at-edges上端で急停止するドロワードラッグするほど動きが減衰する
motion-gesture-alternativeドラッグでしか並べ替えられない行行メニューに Move Up と Move Down を用意する
motion-ease-by-directionease-in で表示されるモーダルイーズアウトで表示・非表示にし、終了時は短くする
motion-strong-ease-outtransition: transform 200ms ease-outtransition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1)
motion-close-faster表示も非表示も 200ms のモーダル表示は 200ms、非表示は 150ms
motion-instant-hover-colorボタンの transition: background-color 200msホバー時の色をすぐに適用する
motion-under-300msドロップダウンの transition={{ duration: 0.5 }}duration: 0.2
motion-no-motion-on-repeatキー入力のたびに animate={{ scale: [1, 1.02, 1] }}入力中はアニメーションさせない
motion-tooltip-delayツールバーの各ツールチップが毎回、遅延時間いっぱい待つ最初のツールチップは待機し、隣のツールチップはすぐに開く
motion-icon-crossfade{copied ? <CheckIcon /> : <CopyIcon />}両方のアイコンをスケールとぼかしでクロスフェードする
motion-icon-morphモーフィングする 2 つのアイコンをクロスフェードする各 motion.path の d を対応するアイコンの値へアニメーションする
motion-blur-the-crossfade切り替えの途中でボタンのラベルが重なる切り替え中に filter: blur(2px) を適用する
motion-blur-untracked-onlyカルーセルのページをスワイプするときにぼかすぼかさない、または勢いよく動かしたカードだけをぼかす
motion-blur-packageカードが飛ぶ間に filter: blur(8px) を適用するカードに useMotionBlur() を使う
motion-blur-ref-on-movermotion.div を囲むラッパーに ref={blur.ref} を指定するmotion.div に ref={blur.ref} を指定する
motion-blur-transparent-hostホストに bg-white rounded-xl を指定する同じクラスを最初の子要素に指定する
motion-blur-bare-layerレイヤーに rotate-3 を指定するレイヤー内の要素に rotate-3 を指定する
motion-blur-echo-for-turnsスピナーに technique: squeeze() を使うtechnique: echo()
motion-blur-smear-limitテキストを含むカードに smear({ limit: 1.3 }) を使うデフォルトの 1.05 を使う smear()、または echo()
motion-blur-one-childアイコンとラベルを含むレイヤーに squeeze() を使うアイコンとラベルを 1 つの要素で囲む
motion-blur-follow-path軌道に blur.render(previous, current) を使うblur.render((t) => at(angle + t * speed * frameTime))
motion-blur-slowdown-shuttershutter: 180 の ¼× デモshutter: 720
motion-blur-custom-techniqueclear() {}、またはレンダー内の technique: ({ layer }) => …paint が設定したすべてのスタイルを削除する clear() と key
motion-press-scalewhileTap={{ scale: 0.8 }}whileTap={{ scale: 0.98 }}
motion-short-staggerstaggerChildren: 0.15staggerChildren: 0.03
motion-exit-in-presence{open && <motion.div exit={{ opacity: 0 }} />}同じ要素を <AnimatePresence> の中に置く
motion-dialogs-stay-mounted{open && <Dialog.Root open>}<Dialog.Root open={open} onOpenChange={setOpen}>
motion-measure-inner1 つのノードに ref と animate={{ height: bounds.height }} を指定する外側のノードをアニメーションさせ、内側のノードに ref を設定する
motion-hold-content-size折りたたまれるサイドバーの中に w-full のコンテンツを置く幅を維持する w-64 のコンテンツを置く
motion-reduced-motionモーション低減設定時にもスライドするドロワー不透明度のクロスフェード
motion-blur-reduced-motionreducedMotion: "never"デフォルトの "user"

報告

重大度。 HIGH は使用またはアクセスを妨げます。最後まで再生されない終了アニメーション、自己計測するコンテナ、ボタンのないジェスチャーなどです。また、読み取り中のテキストにかかるモーションブラーや、squeeze() または svg() の下に子要素が2つあるレイヤーも該当します。

MEDIUM はスクロールするページ、再生される入場アニメーション、固定カーブ上のジェスチャー、または300msを超えるモーションです。また、入力中のモーション、誤ったカーブ、transition: all、切り替え状態に対するキーフレーム、または軽減モーションの無視も該当します。また、条件付きでレンダリングされるダイアログ、折りたたみ中に再配置されるコンテンツ、scale(0) の入場アニメーション、またはフリックを無視するシートも該当します。また、手書きのぼかし、ラッパー上のぼかし参照、不透明なホスト、またはレイヤー上のトランスフォームも該当します。また、Echo以外に表示されるスピナー、1.05を超えるにじみ、または reducedMotion: "never" も該当します。

LOW は色が薄くなるホバー色、強すぎる押下時のスケール、または長いスタガーです。また、誤った原点からのモーション、開くときと同じくらい遅い閉じる動作、遅いツールチップ、または唐突なアイコンの切り替えも該当します。また、単独の ease-out、ぼかしのないクロスフェード、または端の摩擦がないドロワーも該当します。また、カーブ上の2つのトランスフォーム、全速シャッターで減速したデモ、またはスタイルを残したり再構築したりするカスタム手法も該当します。

形式。 根本原因ごとに1件の指摘を記載します。

file:line - [rule-id] what is wrong

続けて Rule | Count | Severity の概要を記載します。HIGH が1件でも残っている場合は Block、それ以外は Approve とします。読んでいないファイルを Approve にしてはいけません。タイミングに依存するチェックではモーションを再生し、残像に依存するチェックでは最速の時点で停止します。実行できなかったチェックはすべて Not verified と記します。

コメント