mcpField
R
react-animations

react-animations

github.com/fcsouzaデザイン・UI2444 回インストール1 週間前に更新
リポジトリ

react-animations について

Framer Motion、React Spring、GSAP、CSS/Tailwindの使い分けと実装を案内する、React開発者向けのアニメーションスキルです。アクセシビリティやパフォーマンスの指針に加え、再利用可能なコンポーネントとバリアントのテンプレートも含みます。

主な機能

  1. 01アニメーションライブラリの選択指針
  2. 02Framer Motionのジェスチャーとレイアウトパターン
  3. 03スクロール、ドラッグ、タイムラインのアニメーション
  4. 04モーション低減へのアクセシビリティ対応
  5. 05パフォーマンスとバンドルサイズの指針
  6. 06再利用可能なアニメーションコンポーネントとバリアント

活用シーン

  1. 01React UIのアニメーション方式を選ぶ
  2. 02スクロール連動や共有要素の遷移を追加する
  3. 03アニメーションのアクセシビリティと性能を確認する
  4. 04再利用可能なゲームUIアニメーションを作る

インストール

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

$npx skills add https://github.com/fcsouza/agent-skills --skill react-animations

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

同梱ファイルboilerplate/motion-components.tsxtemplates/animation-variants.ts

詳細

React アニメーション

Framer Motion(主軸)、React Spring(物理演算)、GSAP(タイムライン)、CSS/Tailwind(シンプルなケース)を使った、本番品質の React アニメーションです。

ライブラリ選択表

ユースケースライブラリ理由
入場/退場アニメーションFramer MotionAnimatePresence がアンマウントを処理します
共有要素のトランジションFramer MotionlayoutId
物理演算ベース(スプリング、バウンス)Framer Motion または React Springspring 設定
複雑なタイムライン/シーケンスGSAPTimeline API
スクロールをトリガーにするFramer Motion(whileInView / useScroll)スクロール用フックを標準搭載しています
シンプルなホバー/フォーカス状態CSS TailwindJavaScript は不要です
ドラッグ&ドロップFramer Motionジェスチャーを標準サポートしています
SVG パスアニメーションGSAP または Framer Motionどちらも SVG をサポートしています
命令型/プログラムによる制御Framer Motion useAnimate最新の命令型 API

基本原則

Matt Perry(Framer Motion の開発者):「アニメーションは宣言的に記述し、命令型で管理すべきではありません。目標の状態を記述すれば、あとはライブラリが処理します。」 Sarah Drasner:「アニメーションは装飾ではなく、コミュニケーションです。すべての動きには目的があるべきです。」

  1. シンプルな場合は CSS、複雑な場合は JS — Tailwind の transition で対応できるなら、それを使いましょう。ホバー状態に Framer Motion を追加する必要はありません
  2. 合成可能なプロパティだけをアニメーションさせる — transform と opacity を使いましょう。width、height、top、left はリフローを引き起こすため使わないでください
  3. 条件付きレンダーは AnimatePresence で囲む — これがないと、退場アニメーションはスキップされます
  4. 連携するアニメーションには Variants を使う — アニメーション状態はインライン値ではなく、コンポーネント外でオブジェクトとして定義しましょう
  5. 共有要素のトランジションには layoutId を使う — Framer Motion が位置間の補間を処理します
  6. ジェスチャー駆動には useMotionValue を使う — アニメーションを駆動する値に useState を使わないでください
  7. 60fps の予算を守る — アニメーションのロジックをレンダーサイクルから切り離し、transform を使いましょう

Framer Motion の主なパターン

基本的なアニメーション
<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  transition={{ duration: 0.3 }}
/>
ジェスチャー状態(whileHover、whileTap)
<motion.button
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
  transition={{ type: 'spring', stiffness: 400, damping: 17 }}
/>
子要素を段階的に表示する Variants
const container = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: { staggerChildren: 0.1 },
  },
};

const item = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
};

<motion.ul variants={container} initial="hidden" animate="visible">
  {items.map((i) => (
    <motion.li key={i} variants={item} />
  ))}
</motion.ul>
退場アニメーションを伴う AnimatePresence

mode プロパティは、入場する要素と退場する要素の動作を制御します。

  • "sync"(デフォルト)— 入場と退場を同時に実行します
  • "wait" — 入場を開始する前に退場を完了します(ページ遷移に適しています)
  • "popLayout" — 退場する要素をレイアウトフローからすぐに取り除きます
<AnimatePresence mode="wait">
  {isVisible && (
    <motion.div
      key="modal"
      initial={{ opacity: 0, scale: 0.9 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.9 }}
    />
  )}
</AnimatePresence>
スクロールをトリガーにするアニメーション用の whileInView

最もシンプルな方法です。スクロール用フックは必要ありません。

<motion.div
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: '-100px' }}
  transition={{ duration: 0.5 }}
/>

スクロールして戻ったときにアニメーションが再生されないよう、viewport.once: true を設定します。

パララックス用の useScroll + useTransform
const { scrollYProgress } = useScroll();
const y = useTransform(scrollYProgress, [0, 1], [0, -200]);

<motion.div style={{ y }} />
共有要素用の layoutId
// In list view
<motion.div layoutId={`card-${id}`}>
  <Thumbnail />
</motion.div>

// In detail view
<motion.div layoutId={`card-${id}`}>
  <FullImage />
</motion.div>
制約付きドラッグ
<motion.div
  drag
  dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
  dragElastic={0.2}
  whileDrag={{ scale: 1.1 }}
/>
useAnimate — 命令型アニメーション(v11+)

プログラムによるシーケンスには、useAnimation よりも useAnimate を優先してください。ref のスコープ内で動作し、そのスコープ内の任意のセレクターを使用できます。

const [scope, animate] = useAnimate();

const handleClick = async () => {
  await animate(scope.current, { scale: 1.2 }, { duration: 0.2 });
  await animate(scope.current, { scale: 1 }, { duration: 0.1 });
};

<div ref={scope}>
  <button onClick={handleClick}>Click me</button>
</div>
MotionConfig — グローバルなアニメーション設定

アプリ(またはサブツリー)をラップして、モーション低減やスプリングのプリセットなどのデフォルトを設定します。

<MotionConfig reducedMotion="user">
  <App />
</MotionConfig>

reducedMotion="user" を指定すると、OS レベルで「視差効果を減らす」設定をしているユーザーのアニメーションが自動的に無効になります。大規模にアクセシビリティへ対応する最も簡単な方法です。

LazyMotion — バンドルサイズの最適化

本番アプリでは、motion を LazyMotion + m に置き換えて、アニメーションエンジンをコード分割しましょう。

import { LazyMotion, domAnimation, m } from 'framer-motion';

<LazyMotion features={domAnimation}>
  <m.div animate={{ opacity: 1 }} />
</LazyMotion>

ドラッグやレイアウトアニメーションが必要な場合は、domAnimation の代わりに domMax を使います。

ゲーム UI のパターン

滑らかなトゥイーンによる体力バー
const motionWidth = useMotionValue(current / max);
const springWidth = useSpring(motionWidth, { stiffness: 200, damping: 30 });

useEffect(() => {
  motionWidth.set(Math.max(0, Math.min(1, current / max)));
}, [current, max]);

<motion.div style={{ scaleX: springWidth, transformOrigin: 'left' }} />
浮かび上がるダメージ数値
<motion.span
  initial={{ opacity: 1, y: 0 }}
  animate={{ opacity: 0, y: -60 }}
  transition={{ duration: 0.8, ease: 'easeOut' }}
  onAnimationComplete={onComplete}
>
  -{damage}
</motion.span>
カードの反転(rotateY)
<motion.div animate={{ rotateY: isFlipped ? 180 : 0 }} style={{ perspective: 1000 }}>
  <div style={{ backfaceVisibility: 'hidden' }}>{front}</div>
  <div style={{ backfaceVisibility: 'hidden', rotateY: 180 }}>{back}</div>
</motion.div>
画面の揺れ(useAnimate)
const [scope, animate] = useAnimate();

const shake = async () => {
  await animate(scope.current, { x: [0, -10, 10, -10, 10, 0] }, { duration: 0.4 });
};

<div ref={scope}>{children}</div>
メニューのスライドイン/スライドアウト
<AnimatePresence>
  {isOpen && (
    <motion.nav
      initial={{ x: -300 }}
      animate={{ x: 0 }}
      exit={{ x: -300 }}
      transition={{ type: 'spring', stiffness: 300, damping: 30 }}
    />
  )}
</AnimatePresence>
インベントリーアイテムのドロップ(スプリング物理演算)
<motion.div
  initial={{ y: -200, opacity: 0 }}
  animate={{ y: 0, opacity: 1 }}
  transition={{ type: 'spring', stiffness: 400, damping: 15 }}
/>

アクセシビリティ

ユーザーのモーション設定を必ず尊重してください。方法は 2 つあります。

1. MotionConfig(アプリに推奨) — コンポーネントツリー全体をラップします。

<MotionConfig reducedMotion="user">
  <App />
</MotionConfig>

2. useReducedMotion フック — コンポーネント単位で制御します。

const shouldReduceMotion = useReducedMotion();

<motion.div
  animate={{ opacity: 1, y: shouldReduceMotion ? 0 : -20 }}
/>

パフォーマンス

  • transform と opacity のみをアニメーションさせます。GPU で合成され、レイアウトやペイントが発生しません
  • 常にアニメーションする要素には will-change: transform を指定します(独立したレイヤーに昇格します)
  • motion.create(Component) を使うと、ラッパーの div を追加せずにカスタムコンポーネントをアニメーションできます
  • layout prop はレイアウトアニメーションを自動的に実行します。大きな DOM ツリーでは負荷が高いため、可能な限り小さなサブツリーに適用します
  • アニメーションを駆動する値には useState より useMotionValue を優先します。モーション値では再レンダリングが発生しません
  • LazyMotion と domAnimation を使うと、フルバンドルと比べて gzip サイズを約 15kb 削減できます

セットアップ

bun add framer-motion

設定不要です。プロバイダーは必要ありません。motion.div を直接インポートして使用します。

グローバルなアクセシビリティ対応を行うには、アプリのルートをラップします。

import { MotionConfig } from 'framer-motion';

<MotionConfig reducedMotion="user">
  <App />
</MotionConfig>

ボイラープレート

boilerplate/motion-components.tsx — すぐに使えるコンポーネント:FadeIn、SlideIn、ScaleIn、StaggerList、FloatingNumber、HealthBar、AnimatedCard

templates/animation-variants.ts — 再利用可能なバリアントオブジェクトとスプリングトランジションのプリセット

関連項目

  • vercel-react-best-practices — React のパフォーマンスパターン
  • ui-ux-game — ゲームの HUD と UI のパターン
  • frontend-design — コンポーネント設計と Tailwind のパターン

落とし穴とアンチパターン

  • レイアウトを発生させるプロパティ(width、height、top、left)をアニメーションさせる — 代わりに scaleX/scaleY または layout prop を使います
  • exit prop の使用時に AnimatePresence を忘れる — ラッパーがないと、終了アニメーションは実行されずにスキップされます
  • レンダー中にモーション値を作成する — useMotionValue フックを使います。レンダー中に作成するとメモリリークが発生します
  • 同じ要素で CSS の transition と Framer Motion を併用する — 競合するため、どちらか一方を選びます
  • アニメーションを過剰に使う — すべての操作をアニメーションさせると感覚的な負荷が高くなります。装飾ではなく、伝達のためにアニメーションを使います
  • initial を指定せずにマウント時にアニメーションさせる — アニメーション前にコンポーネントが一瞬表示されます。必ず initial を設定します
  • useAnimation を使う — 非推奨です。命令型アニメーションには代わりに useAnimate を使います
  • 大規模な layout アニメーション — 深くネストされたツリーで layout prop を使うと、負荷の高い再計算が発生します。可能な限り小さなサブツリーに適用します
  • アクセシビリティ対応を省略する — 必ず MotionConfig reducedMotion="user" または useReducedMotion を使います

参考資料

  • Framer Motion のドキュメント — https://motion.dev
  • Matt Perry — Framer Motion の開発者。API 設計に関する講演
  • Sarah Drasner — 「SVG Animations」、アニメーション設計パターン
  • GSAP のドキュメント — https://gsap.com

コメント