mcpField
M
micro-interaction

micro-interaction

github.com/iart-aiデザイン・UI632.6k 回インストール18 週間前に更新
リポジトリ

micro-interaction について

ボタンのフィードバックからドロワーやリストの遷移まで、機能的なUIモーションの実装を支援するスキルです。CSSやFramer Motionの手法、タイミング、動きを減らす設定への対応、状態ごとの検証を扱います。

主な機能

  1. 01ホバー・押下・フォーカスの反応
  2. 02表示・終了のトランジション
  3. 03リストと共有要素のモーション
  4. 04CSSとFramer Motionの手法
  5. 05動きを減らす設定の指針

活用シーン

  1. 01ボタンやトグルへの反応追加
  2. 02トースト、ドロワー、モーダルのアニメーション
  3. 03読み込みから完了への状態表示

インストール

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

$npx skills add https://github.com/iart-ai/web-animation-skills --skill micro-interaction

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
references/
css-recipes.md
framer-motion-recipes.md

詳細

マイクロインタラクション(UI モーション)

インターフェースに応答性と躍動感を与える、小さく機能的なモーションです。ホバー/押下/フォーカス時のフィードバック、トグル、トースト、ドロワー、リストやレイアウトのアニメーションなどが含まれます。目的は装飾ではなく、フィードバックと連続性です。

使用する場面

  • ホバー/押下/フォーカス時のフィードバック、トグル、チェックボックス、いいね/ハートボタン
  • トースト/スナックバー、ドロワー、モーダル、ツールチップ、アコーディオン(表示/非表示)
  • リストへの追加/削除/並べ替え、共有要素(「マジックムーブ」)によるレイアウト遷移
  • 読み込み → 成功 → エラーの状態遷移

原則(すべてに適用)

  • 再生時間:UI のマイクロインタラクションは 100–250ms に収めます。約 400ms を超えると、クリックへの応答が遅く感じられます。
  • アニメーションは transform と opacity のみにします。これらは GPU で合成されるため、レイアウトやペイントが発生しません。width/height/top/left のアニメーションは避け、代わりに scale またはレイアウトアニメーションを使います。
  • 押下時には即座にフィードバックを返します。タップ時に高速なスプリングで scale: 0.96 にします。
  • 表示と非表示でタイミングを変えます。表示は少し遅め(ease-out)、非表示は速め(ease-in)にします。要素は自然に現れ、すばやく消えるようにします。
  • prefers-reduced-motion を尊重します。必須でないモーションは制御し、大きな移動は省いて、透明度の変化は維持します。
  • イージングのデフォルト:表示には cubic-bezier(0.16, 1, 0.3, 1) の ease-out、ほどよいオーバーシュートには cubic-bezier(0.34, 1.56, 0.64, 1)、標準的な移動には cubic-bezier(0.4, 0, 0.2, 1) を使います。

Framer Motion(motion/react)の基本

v11 以降、パッケージは motion/react としてインポートします(従来の framer-motion も引き続き使用できます)。

ジェスチャーとスプリング
import { motion } from "motion/react";

<motion.button
  whileHover={{ scale: 1.03 }}
  whileTap={{ scale: 0.96 }}
  whileFocus={{ boxShadow: "0 0 0 3px rgba(59,130,246,.5)" }}
  transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>

スプリングの感覚:stiffness を高くすると速くなり、damping を高くすると弾みが小さくなります。stiffness: 400, damping: 30 は、きびきびとした UI のデフォルト値です。目に見える弾みを加えるには、damping を低くします(例:damping: 12)。

AnimatePresence による表示/非表示

終了アニメーションを使うには、条件付きでレンダリングする子要素を、それぞれ安定した key とともに AnimatePresence で囲む必要があります。

import { AnimatePresence, motion } from "motion/react";

<AnimatePresence>
  {open && (
    <motion.div
      key="panel"
      initial={{ opacity: 0, y: 8 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: 8 }}
      transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
    />
  )}
</AnimatePresence>

トーストやリストには mode="popLayout" を使うと、削除された項目が終了アニメーション中にスペースを占有しません。

レイアウトアニメーション(強力な機能)

layout は、レイアウトの変更(位置/サイズ)を変形を使って自動的にアニメーション化します。並べ替え、カードの展開、グリッドとリストの切り替えに最適です。

<motion.li layout transition={{ type: "spring", stiffness: 500, damping: 40 }} />

コンポーネント間で共有要素を遷移させるには、2 つの要素に同じ layoutId を指定します。一方の要素がマウントされ、もう一方がアンマウントされる際に、Framer Motion が 1 つの要素のようにアニメーション化します。

{!open && <motion.div layoutId="card" onClick={() => setOpen(true)} />}
<AnimatePresence>
  {open && <motion.div layoutId="card" />} {/* "magic moves" from the source */}
</AnimatePresence>

注意点:通常の layout 要素では、拡大縮小時に border-radius とテキストが歪みます。逆方向にスケールする必要がある直下の子要素にも layout を追加し、borderRadius/boxShadow をモーション値として使うのがおすすめです。または、位置のみをアニメーション化する layout="position" を使います。

純粋な CSS の方法(JS 不要、離散プロパティを含む)

最新の CSS では、ライブラリを使わずに表示/非表示や、display の切り替えまでアニメーション化できます。

.toast {
  transition: opacity 0.2s ease, transform 0.2s ease;
  /* allow animating to/from display:none and from initial render */
  transition-behavior: allow-discrete;
}
.toast[hidden] { display: none; }

/* animate FROM these values on first render / when entering */
@starting-style {
  .toast { opacity: 0; transform: translateY(8px); }
}

@starting-style は、要素が最初に表示されるときにアニメーションで現れるよう、「開く前」の値を定義します。transition-behavior: allow-discrete を指定すると、display(およびポップオーバー/ダイアログの overlay)も遷移に参加し、要素が削除される前に終了アニメーションを実行できます。Chrome/Edge/Safari では標準で利用できます。古いブラウザー向けには、アニメーションなしのフォールバックを用意してください。

モーション低減の設定を必ず考慮してください。

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

装飾ではなく状態を表現する

読み込み → 成功 → エラーの状態遷移では、中断して切り替えるのではなく、(たとえば、ボタンがスピナーからチェックマークに変化するように)一連のモーションとして表現します。要素をマウントしたまま状態間をアニメーション化し、ユーザーの視線が同じオブジェクトを追えるようにします。

配布と検証(スタンドアロン HTML)

パッケージ済みのヘルパー(scripts/):scripts/seek-shot.sh anim.html 0 1.5 3 は ?t=N ハーネスを固定して各時点のスクリーンショットを撮影します。scripts/contact-sheet.sh sheet.png frame-*.png は画像をタイル状に並べて、ひと目で確認できるようにします。詳しくは scripts/README.md を参照してください。

トグル、いいねボタン、トースト、ドロワーなどの自己完結型インタラクションデモでは、成果物はブラウザーで直接開ける単一の HTML ファイルです。純粋な CSS によるインタラクションはそのまま提供します。Framer Motion のデモでは、React と motion を CDN(esm.sh)からインラインモジュールに読み込みます。ビルド手順は不要です。適切なのは単一ファイルです。バンドラーは使わないでください。

出力仕様:

  • 1 つの .html ファイル:マークアップと、CSS トランジション/@starting-style のいずれか、または CDN から motion をインポートするインラインの <script type="module">。
  • スクリーンショット用に確定した終了状態にできる方法。インタラクションは時間ではなく状態によって決まるため、時計ではなく状態を固定します。

シークハーネス — 状態を決定論的に固定します。 マイクロインタラクションの「フレーム」は、その状態(アイドル/ホバー/押下/開いた状態)です。読み込み時に ?state=open を適用すると、対象の状態に設定され、落ち着いた状態をスクリーンショットに収められます。

<script>
  const s = new URLSearchParams(location.search).get("state");
  if (s) document.documentElement.dataset.state = s;   // CSS keys off [data-state="open"]
  // Framer Motion: set the controlled prop from `s` (e.g. const [open]=useState(s==="open"))
  // For a CSS @keyframes loop instead, freeze it: el.style.animationDelay=(-N)+"s"; el.style.animationPlayState="paused";
  window.__ready = true;
</script>

検証ループ — レンダリング → 状態の設定 → スクリーンショット → 確認: 意味のある各状態(?state=idle、?state=hover、?state=open)を開いてスクリーンショットを撮影し、忠実度(押下時のフィードバックが即座に伝わること、アンマウント前に終了アニメーションが実行されること)と表示上の問題(border-radius/テキストを歪ませる layout、終了後もスペースを占有するトースト、FOUC、カクつき)を確認します。ヘッドレスツールはどれでも使用できます。

npx playwright screenshot --wait-for-timeout=400 "file://$PWD/demo.html?state=open" open.png

完了前の確認事項:

  1. スタンドアロンで開けること — コンソールエラーがなく、CDN の React/motion(使用している場合)が読み込まれること。
  2. ?state=(または制御されたプロパティ)によって、状態を決定論的に固定し、落ち着いた状態にできること。
  3. 各状態(アイドル/アクティブ/開いた状態)のスクリーンショットが要件と一致し、表示上の問題がないこと。
  4. prefers-reduced-motion に対応していること — 大きな動きは省き、透明度やフィードバックは維持すること。
  5. イージングに意図があること — 開始時はイーズアウト、終了時はイーズイン、時間は 100〜250ms の範囲にすること。

クイックリファレンス

必要なもの方法
押下時のフィードバックwhileTap={{ scale: 0.96 }} のスプリング 400/30
開始/終了AnimatePresence + initial/animate/exit
並べ替え/サイズ変更layout プロパティ(スプリング)
マジックムーブ共有 layoutId
トーストのスタックAnimatePresence mode="popLayout"
JavaScript なしの開始@starting-style + トランジション
display:none へのアニメーションtransition-behavior: allow-discrete
アクセシビリティ常に prefers-reduced-motion のガードを設ける

参考ファイル

  • references/framer-motion-recipes.md — バリアント + スタッガー、AnimatePresence のモード、layout/layoutId によるマジックムーブ、ドラッグして並べ替える Reorder、ドラッグの制約、ジェスチャーの組み合わせ、useReducedMotion。
  • references/css-recipes.md — @starting-style、transition-behavior: allow-discrete、ポップオーバー/ダイアログの終了アニメーション、キーフレームによるスピナー/トグル、アニメーション可能なカスタムプロパティ向けの @property、prefers-reduced-motion のパターン。

コメント