web-animation-framer-motion について
React 開発者向けの Motion アニメーションパターンガイドです。コンポーネントの状態、レイアウト変更、スクロール連動の効果を扱い、バリアントによる制御、出入りのアニメーション、ジェスチャー、動きを抑える設定を解説します。
主な機能
- 01motion コンポーネントで状態を定義
- 02バリアントによる子要素の制御
- 03コンポーネントの退出アニメーション
- 04レイアウト変更と共有要素のアニメーション
- 05スクロールやジェスチャーによる駆動
- 06動きを抑える設定への対応
活用シーン
- 01条件付きコンポーネントの出入りを演出
- 02リスト項目に順次アニメーションを適用
- 03タブやカードのレイアウト変更を演出
- 04スクロール起動やジェスチャー連動の効果を作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-animation-framer-motionインストール後、Claude Code で/web-animation-framer-motionを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/core.mdlayout.mdscroll.mdsequences.mdsvg.md詳細
モーションアニメーションのパターン
クイックガイド:
motion.*コンポーネントは、initial、animate、exit、transitionの各プロパティを受け取ります。 バリアントを使うと、これらの状態を名前付きのセットにまとめ、親コンポーネントから制御できます。削除されたコンポーネントを、退場アニメーションが終わるまでマウント状態に保つのがAnimatePresenceです。layoutとlayoutIdは、レイアウトの変更に合わせて FLIP アニメーションを実行します。useScrollとuseInViewは、スクロール位置に応じたモーションを制御します。x、y、scale、rotate、opacityをアニメーションさせ、ルートでMotionConfig reducedMotion="user"を設定します。
インポート:
import { motion } from "motion/react"— v11 でパッケージ名がframer-motionから変更されました。
詳細リソース:
- examples/core.md — モーションコンポーネント、バリアント、AnimatePresence、ジェスチャー、モーション低減
- examples/layout.md —
layout、layoutId、展開可能なカード、タブインジケーター - examples/scroll.md — スクロール進捗、表示時のアニメーション、視差効果
- examples/sequences.md —
useAnimationの連鎖、キーフレーム配列、stagger()による調整 - examples/svg.md —
pathLengthを使った描画エフェクト - reference.md — v11/v12 の移行、トランジションのプリセット、プロパティとフックの一覧
該当するパターン
- 要素が React ツリーに追加または削除される — アニメーションさせるには、要素を
AnimatePresenceで囲み、keyを付ける必要があります。examples/core.md を参照してください。 - サイズや位置が変わる間も要素がマウントされている —
layoutが変更前後の状態を計測し、その差をアニメーションさせます。examples/layout.md を参照してください。 - 同じ要素が、異なるタイミングで2か所に表示される — 共通の
layoutIdを指定すると、一方からもう一方へ移動します。examples/layout.md を参照してください。 - スクロール位置に応じて値が変化する —
useTransformと組み合わせたuseScrollを使うか、1回限りのトリガーにはwhileInViewを使います。examples/scroll.md を参照してください。 - React の状態以外の何かがアニメーションをトリガーする — タイマー、レスポンス、エラーなどの場合、
useAnimationを使うと命令型のハンドルを利用できます。examples/sequences.md を参照してください。
<critical_requirements>
Motion のコードを書く前に
削除時にアニメーションするものは、すべて AnimatePresence で囲みます。 条件が false になると、React はその瞬間にノードをアンマウントするため、ノードをツリー内に保持してアニメーションの完了を待つラッパーがなければ、exit を実行する対象が残りません。
AnimatePresence の直接の子には、すべて安定した一意の key を指定します。 AnimatePresence はこのキーを使って退出する要素を照合します。インデックスをキーにすると、リストの変更時に別の項目を指すことになり、誤った要素が退出アニメーションします。
x、y、scale、rotate、opacity をアニメーションさせます。 Motion はこれらをコンポジターが扱う transform と opacity に書き込みます。一方、height、width、marginTop はフレームごとにレイアウトを再実行します。
アプリのルートに MotionConfig reducedMotion="user" を設定します。 これにより、OS で視差効果を減らす設定にしたユーザーには transform とレイアウトのアニメーションが無効になりますが、opacity と色は引き続き機能します。そのため、状態の変化が瞬時に切り替わるのではなく、認識しやすいままになります。
</critical_requirements>
自動検出: motion/react、framer-motion、motion.div、AnimatePresence、MotionConfig、 LayoutGroup、LazyMotion、layoutId、whileHover、whileTap、whileDrag、whileInView、useAnimation、 useScroll、useTransform、useMotionValue、useSpring、useInView、usePageInView、useReducedMotion、 useDragControls、staggerChildren、delayChildren、Variants
対象:
- React のマウントライフサイクルに連動する入場、退出、存在アニメーション
- variants とスタガーを使い、1 つの親から複数の要素を調整すること
- ホバー、タップ、ドラッグ、フォーカスなどのジェスチャー駆動型モーション
- スクロールをトリガーとする効果や、スクロールに連動する効果
- レイアウトの変更や、コンテナー間の共有要素トランジション
- React の状態ではモデル化されないイベントから実行する命令型シーケンス
別の方法で対応するもの:
- マウント、アンマウント、移動を伴わない、ホバー時の色やフォーカスリングなどの宣言型状態フィードバック。コンポーネントラッパーを使わず、スタイリング層で対応できます
- 成果物がコンポーネント状態の集合ではなく、スクラブ可能なタイムラインとなる、フレーム単位のタイムライン作成
- ブラウザーが退出ページと入場ページを一緒に合成する、ドキュメント全体のビュー切り替え
Motion は時間ではなく状態をアニメーションさせます。コンポーネントで各状態の見た目を宣言すると、Motion が補間、割り込み、引き継がれる速度を処理します。そのため、途中で割り込まれたスプリングは再開時に最初からではなく、中断した位置から続きます。
<decision_framework>
Motion を採用する価値がある場合
次のいずれかに該当する場合は、コンポーネントラッパーとバンドルサイズの増加を受け入れて Motion を使う価値があります。
- 要素がツリーから削除されるため、削除前にアニメーションさせる必要がある
- 開始値が明示的に指定されるのではなく、レイアウトの変更、ドラッグの終了、割り込まれたスプリングなどから測定される
- スタガーや退出時の逆再生を使い、1 つのトリガーから複数の要素を調整する必要がある
- アニメーションする値が、スクロール進行度などの連続入力から導出される
どれにも該当しないなら、両端の値があらかじめわかっている状態変化であり、ランタイムは必要ありません。
Variants または直接指定する props
複数の要素でアニメーションを共有する場合、または親から子のタイミングを制御する必要がある場合は、variants を使います。staggerChildren と delayChildren は variants を使う場合にのみ使用でき、子は何も渡されなくても親の variant 名を継承します。2 つの状態を持つ単一の要素には、initial/animate をインラインで記述するほうが明快です。
</decision_framework>
基本パターン
パターン 1:Motion コンポーネント
motion. を接頭辞にした HTML または SVG タグは、initial、animate、exit、transition を受け取ります。
import { motion } from "motion/react";
export const FadeIn = ({ children }: { children: React.ReactNode }) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
);
y は transform にコンパイルされます。marginTop や top を使うと、フレームごとにレイアウトが再計算されます。
コード全体:examples/core.md
### パターン 2:オーケストレーションのためのバリアント
バリアントはアニメーションの状態に名前を付けるため、親要素が名前で子要素を動かし、タイミングを制御できます。
import { motion, type Variants } from "motion/react";
const containerVariants: Variants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { staggerChildren: 0.1 } },
};
const itemVariants: Variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
子要素は親要素の現在のバリアント名を継承するため、item 要素には独自の animate プロパティを指定しません。staggerDirection: -1 は終了時の連鎖を逆順にするため、リストは末尾から順にほどけるように消えていきます。
完全なコード:examples/core.md
パターン 3:AnimatePresence
削除されたコンポーネントを、exit アニメーションが終わるまでツリー内に保持します。
import { AnimatePresence, motion } from "motion/react";
<AnimatePresence>
{isOpen && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
/>
)}
</AnimatePresence>
mode="sync"(デフォルト)では終了と開始が重なります。mode="wait" では終了が終わるまで開始を待つため、ページ遷移に適しています。mode="popLayout" では終了する要素をフローから取り除き、残りの兄弟要素が所定の位置へアニメーションします。
完全なコード:examples/core.md
パターン 4:ジェスチャー
whileHover、whileTap、whileFocus、whileDrag は、ジェスチャー中に維持され、ジェスチャーが終わると元に戻る状態を表します。
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
/>
ドラッグでは、drag、dragConstraints(ref またはピクセル単位のボックス)、境界を越えたときの抵抗を指定する dragElastic を追加します。要素自体以外からドラッグを開始する場合は、useDragControls を使用します。
完全なコード:examples/core.md
パターン 5:レイアウトアニメーション
layout は React のコミット前後に要素を測定し、その差をアニメーションします。そのため、flex の順序、グリッド上の配置、コンテンツのサイズを変更すると、開始値を指定しなくてもアニメーションします。
<motion.div layout transition={LAYOUT_SPRING}>
<motion.h2 layout="position">Title</motion.h2>
</motion.div>
{activeTab === tab && <motion.div layoutId="indicator" />}
layout="position" は子要素の位置をアニメーションしますが、スケールは変えません。これにより、親要素のサイズ変更時にテキストが伸びるのを防げます。layoutId は同時にマウントされることのない 2 つの要素を対応付け、一方からもう一方へアニメーションします。ID はグローバルなため、コンポーネントがページ上に複数回表示される場合は、LayoutGroup id={...} でスコープを指定します。
完全なコード:examples/layout.md
パターン 6:スクロールに連動するモーション
whileInView は要素が表示領域に入ったときに一度だけ実行されます。useTransform と組み合わせた useScroll は、連続的なスクロールの進行度を値にマッピングします。
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
/>;
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"],
});
const y = useTransform(scrollYProgress, [0, 1], [-100, 100]);
useScroll が返す値はモーション値であり、React の外部で更新されます。そのため、スクロールの各フレームで再レンダリングは発生しません。
完全なコード:examples/scroll.md
パターン 7:スプリングとトゥイーンのトランジション
// Springs: physics, no fixed duration, survive interruption
const BOUNCY = { type: "spring", stiffness: 300, damping: 10 };
const SNAPPY = { type: "spring", stiffness: 500, damping: 30 };
// Tweens: fixed duration and curve
const ENTER = { type: "tween", ease: "easeOut", duration: 0.3 };
const EXIT = { type: "tween", ease: "easeIn", duration: 0.2 };
スプリングは、ボタン、カード、ドラッグなど、ユーザーが途中で中断できるものに適しています。中断時にも速度が引き継がれるためです。トゥイーンは、モーダルや、ほかの要素と連動するページ変更のように、決まった時間内に完了させる必要があるモーションに適しています。
プリセット:reference.md
パターン 8:useAnimation による命令型制御
タイマー、レスポンス、エラーなど、React の状態で表されないものをきっかけにアニメーションを開始します。
const controls = useAnimation();
useEffect(() => {
if (hasError) {
controls.start({ x: [0, -10, 10, -10, 0], transition: { duration: 0.3 } });
}
}, [hasError, controls]);
<motion.div animate={controls}>{children}</motion.div>;
controls.start に配列を渡すと、各ステップが直前のステップの完了を待ってから順に実行されます。
完全なコード:examples/sequences.md
パターン 9:モーションの軽減
// Whole app
<MotionConfig reducedMotion="user">{children}</MotionConfig>
// One component, where the reduced form is different rather than absent
const shouldReduceMotion = useReducedMotion();
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: shouldReduceMotion ? 0.2 : 0.5 }}
/>
reducedMotion="user" は、不透明度と色をそのままにして、変形とレイアウトのアニメーションを無効にします。そのため、アプリでは変更があったことを引き続き示せます。変形を単に取り除くのではなく、モーションを軽減した場合に独自の値が必要であれば、useReducedMotion を使用します。
完全なコード:examples/core.md
パターン 10:スタガーの調整とタブの表示状態
stagger()(v12 以降)は staggerChildren ではなく delayChildren に指定します。これは各子要素の遅延を計算する関数を返すため、連鎖を中央から開始したり、イージング曲線に沿って実行したりできます。
import { stagger, usePageInView } from "motion/react";
const transition = {
delayChildren: stagger(0.05, { from: "center", ease: "easeOut" }),
};
// from: "first" (default) | "center" | "last" | an index
const isPageVisible = usePageInView(); // v12.19+, true on the server
usePageInView はタブの表示状態を報告します。ループするアニメーションはこれに応じて制御すると、バックグラウンドのタブでフレームを消費し続けることを防げます。
完全なコード:examples/sequences.md
<red_flags>
注意すべき点
実行時に問題が発生します:
AnimatePresenceが上位にないコンポーネントでexitを指定する — React が先にノードを削除するため、プロパティが実行されません — 条件付きレンダリングをラップしますAnimatePresenceの直接の子として React Fragment を指定する — Fragment にはkeyを指定できないため、何も追跡されず、すべての exit がスキップされます — 各要素に個別のkey付き条件分岐を指定します- アニメーションするリストの子に
key={index}を指定する — 挿入や並べ替えの際にキーが別の項目を指すため、誤った要素がアニメーションして消えます — 安定した ID をキーにします height、width、top、left、margin、paddingをアニメーションする — フレームごとにレイアウトが再計算されます —scaleとx/yをアニメーションし、拡大を開始する位置にtransformOriginを設定しますlayoutを指定した親のテキストの子にlayout="position"がない — 子がボックスと一緒に拡大し、文字が目に見えて引き伸ばされます — 各子にこのプロパティを追加します- 動きを減らす設定への対応がない — OS で動きを無効にしたユーザーにも大きく移動するアニメーションが表示されます — ルートに
MotionConfig reducedMotion="user"を設定します
予期しない動作:
layoutIdはグローバルなので、同じコンポーネントのインスタンスが同じページに 2 つあると競合します —LayoutGroup id={...}でスコープを設定しますmode="wait"は exit と enter を順番に実行するため、体感される遅延は両方の所要時間の合計になりますwhileInViewはviewportで設定し、useScrollはoffsetで設定します — 2 つのプロパティ名は相互に置き換えられません- アニメーションするのは
motion.path、motion.circleとその兄弟要素だけです。motion.svg内の通常の<path>は動作しません - サーバーでレンダリングする間、
useInViewはfalseを返すため、初期状態を表示に設定しない限り、サーバーのマークアップは非表示状態になります - Motion の値は意図的に再レンダリングを発生させません — 副作用を実行する必要がある場合は、
useMotionValueEventで値を読み取ります - 1 つの要素で
dragとlayoutを使うと、同じ transform を取り合います — ドラッグ中はレイアウトを無効にします - 剛性が高く減衰が小さいスプリングは、実際のコンテンツがはみ出して切れるほど大きくオーバーシュートすることがあります。短いプレースホルダー文字列ではこの問題に気づけません
- 手動で設定した
willChangeは、アニメーション対象をすでにレイヤーに昇格させている Motion 独自のレイヤー管理と競合します