micro-interaction について
ボタンのフィードバックからドロワーやリストの遷移まで、機能的なUIモーションの実装を支援するスキルです。CSSやFramer Motionの手法、タイミング、動きを減らす設定への対応、状態ごとの検証を扱います。
主な機能
- 01ホバー・押下・フォーカスの反応
- 02表示・終了のトランジション
- 03リストと共有要素のモーション
- 04CSSとFramer Motionの手法
- 05動きを減らす設定の指針
活用シーン
- 01ボタンやトグルへの反応追加
- 02トースト、ドロワー、モーダルのアニメーション
- 03読み込みから完了への状態表示
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ iart-ai/ web-animation-skills --skill micro-interactionインストール後、Claude Code で/micro-interactionを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/css-recipes.mdframer-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
完了前の確認事項:
- スタンドアロンで開けること — コンソールエラーがなく、CDN の React/
motion(使用している場合)が読み込まれること。 ?state=(または制御されたプロパティ)によって、状態を決定論的に固定し、落ち着いた状態にできること。- 各状態(アイドル/アクティブ/開いた状態)のスクリーンショットが要件と一致し、表示上の問題がないこと。
prefers-reduced-motionに対応していること — 大きな動きは省き、透明度やフィードバックは維持すること。- イージングに意図があること — 開始時はイーズアウト、終了時はイーズイン、時間は 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のパターン。