T-CREATOR

Motion(旧 Framer Motion)× GSAP 併用/置換の判断基準:大規模アニメの最適解を探る

Motion(旧 Framer Motion)× GSAP 併用/置換の判断基準:大規模アニメの最適解を探る

React ベースのプロジェクトで、複雑なアニメーションを実装する際に「Motion(旧 Framer Motion)と GSAP のどちらを選ぶべきか」という悩みは、多くの開発者が直面する課題です。 さらに、「両方を併用すべきか」「どちらかに統一すべきか」という判断も難しいところですね。

本記事では、Motion(旧 Framer Motion)と GSAP の特性を踏まえ、大規模アニメーション実装における併用・置換の判断基準を具体的に解説します。 パフォーマンス、開発効率、保守性の観点から、プロジェクトに最適な選択ができるようになることを目指しましょう。

背景

Motion(旧 Framer Motion)と GSAP の位置づけ

React エコシステムにおけるアニメーションライブラリは、用途やアプローチによって大きく異なります。 Motion(旧 Framer Motion)は React のコンポーネント指向に最適化されたライブラリで、宣言的な API が特徴です。 一方、GSAP はフレームワーク非依存の命令的なアニメーションエンジンで、高度な制御と圧倒的なパフォーマンスを誇ります。

以下の図は、両ライブラリの基本的な位置づけと適用領域を示しています。

mermaidflowchart TB
    subgraph react["React エコシステム"]
        motion["Motion<br/>(旧 Framer Motion)"]
        component["コンポーネント<br/>ベース UI"]
    end

    subgraph universal["フレームワーク非依存"]
        gsap["GSAP"]
        dom["DOM 直接操作"]
    end

    motion -.->|最適化| component
    gsap -.->|高性能| dom

    component -->|統合| app["React アプリ"]
    dom -->|統合| app

    style motion fill:#0055ff,color:#fff
    style gsap fill:#88ce02,color:#000

図の要点:

  • Motion はコンポーネントと密結合し、React の仮想 DOM と協調動作します
  • GSAP は DOM 直接操作により、フレームワークの制約を受けません
  • 両者は異なるアプローチで同じ React アプリに統合可能です

ライブラリの進化と現状

Framer Motion は 2024 年に Motion へとリブランディングされ、パフォーマンス最適化とバンドルサイズの削減が進みました。 GSAP は長年の実績を持ち、v3 以降はモジュール化が進み、必要な機能のみを導入できるようになっています。

どちらも活発に開発が続けられており、大規模プロジェクトでの採用実績も豊富です。

課題

大規模アニメーション実装における 3 つの課題

大規模なアニメーション実装では、以下の 3 つの課題が顕在化します。

# 課題 1:パフォーマンスの限界

複数のアニメーションが同時に動作する場合、ライブラリの選択がパフォーマンスに直結します。 特に、100 個以上の要素を同時にアニメーションさせるケースでは、フレームレートの低下が顕著になりますね。

# 課題 2:開発効率と学習コスト

チーム開発では、メンバーの習熟度や開発スピードが重要です。 宣言的な Motion は直感的ですが、複雑な制御には限界があるでしょう。 命令的な GSAP は柔軟性が高い反面、学習コストがかかります。

# 課題 3:保守性とコードの一貫性

複数のライブラリを併用すると、コードの一貫性が失われ、保守が困難になりがちです。 「どの場面でどのライブラリを使うか」という基準が曖昧だと、チーム全体の開発効率が低下してしまいます。

以下の図は、これらの課題の関係性を示しています。

mermaidflowchart TD
    scale["大規模アニメーション実装"]

    scale --> perf["課題1:<br/>パフォーマンス限界"]
    scale --> dev["課題2:<br/>開発効率と学習コスト"]
    scale --> maint["課題3:<br/>保守性と一貫性"]

    perf --> result["プロジェクト<br/>目標未達成"]
    dev --> result
    maint --> result

    result --> decision["ライブラリ選択<br/>判断基準が必要"]

    style scale fill:#ff6b6b,color:#fff
    style result fill:#ffa500,color:#fff
    style decision fill:#4ecdc4,color:#fff

図の要点:

  • 3 つの課題は互いに影響し合い、総合的な判断が必要です
  • 適切な判断基準がないと、プロジェクト全体に影響します
  • ライブラリ選択は技術選定の中核となる重要な決定事項です

解決策

併用・置換の判断フレームワーク

Motion と GSAP の選択には、以下の 4 つの判断軸を設けることで、明確な基準を持つことができます。

# 判断軸 1:アニメーション種別による選択

アニメーションの種類によって、最適なライブラリは異なります。

Motion が適している場合:

  • コンポーネントのマウント/アンマウント時のアニメーション
  • レイアウトアニメーション(要素の位置・サイズ変更)
  • インタラクティブな UI フィードバック(ホバー、タップ)
  • ページ遷移アニメーション

GSAP が適している場合:

  • 複雑なタイムラインベースのアニメーション
  • SVG 描画アニメーション
  • スクロールトリガー連動アニメーション
  • 100 個以上の要素を同時制御するアニメーション

以下は、判断フローを示した図です。

mermaidflowchart TD
    start["アニメーション要件"]

    start --> q1{"React<br/>コンポーネント<br/>ライフサイクル<br/>に連動?"}

    q1 -->|Yes| q2{"レイアウト<br/>変更を<br/>自動追従?"}
    q1 -->|No| q3{"複雑な<br/>タイムライン<br/>制御が必要?"}

    q2 -->|Yes| motion_choice["Motion を選択"]
    q2 -->|No| q4{"要素数は<br/>100個以上?"}

    q3 -->|Yes| gsap_choice["GSAP を選択"]
    q3 -->|No| q5{"スクロール<br/>トリガー<br/>連動?"}

    q4 -->|Yes| gsap_choice
    q4 -->|No| motion_choice

    q5 -->|Yes| gsap_choice
    q5 -->|No| motion_choice

    style motion_choice fill:#0055ff,color:#fff
    style gsap_choice fill:#88ce02,color:#000

図で理解できる要点:

  • コンポーネントライフサイクル連動なら Motion が第一候補です
  • 複雑なタイムライン制御やスクロール連動では GSAP が有利です
  • 要素数が多い場合はパフォーマンス面で GSAP を検討しましょう

# 判断軸 2:パフォーマンス要件による選択

アニメーションのパフォーマンス要件は、ライブラリ選択の重要な指標です。

以下の表は、パフォーマンス要件別の推奨ライブラリを示しています。

#パフォーマンス要件同時アニメーション要素数フレームレート目標推奨ライブラリ理由
1低負荷 UI10 個以下30fps 以上MotionReact 統合が容易
2標準 UI10-50 個60fpsMotion / GSAPどちらも対応可能
3高負荷 UI50-100 個60fpsGSAP最適化されたエンジン
4超高負荷100 個以上60fpsGSAPGPU 加速と細かい制御
5リアルタイム変動60fps 必須GSAPrequestAnimationFrame の直接制御

# 判断軸 3:開発効率と保守性による選択

チーム開発では、コードの可読性と保守性が長期的な成功を左右します。

Motion の利点:

  • 宣言的 API で直感的
  • TypeScript サポートが充実
  • React DevTools で状態確認可能
  • コンポーネント単位で完結

GSAP の利点:

  • 細かい制御が可能
  • プラグインエコシステムが豊富
  • フレームワーク移行時も継続利用可能
  • 複雑なアニメーションのデバッグツールが充実

# 判断軸 4:バンドルサイズと依存関係

本番環境のバンドルサイズは、初期読み込み速度に直結します。

以下は、基本的なインストールサイズの比較です。

typescript// Motion(旧 Framer Motion)の基本導入
// バンドルサイズ: 約 35KB (gzip圧縮後)
typescriptimport { motion } from 'motion/react';
typescript// GSAP の基本導入
// バンドルサイズ: 約 50KB (gzip圧縮後、コア機能のみ)
typescriptimport { gsap } from 'gsap';
typescript// GSAP のプラグイン追加時
// ScrollTrigger: +15KB
// Draggable: +20KB
// MotionPathPlugin: +10KB
typescriptimport { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

バンドルサイズの判断基準:

  • シンプルなアニメーションのみ → Motion
  • スクロール連動やドラッグが必要 → GSAP(プラグイン含めても妥当)
  • 両方必要 → 併用(ただし合計 100KB 超に注意)

併用パターンの設計指針

Motion と GSAP を併用する場合、明確な役割分担が重要です。

# 推奨併用パターン

以下のパターンでは、両ライブラリの強みを活かせます。

パターン 1:UI 層とコンテンツ層の分離

  • Motion: UI コンポーネント(ボタン、モーダル、ナビゲーション)
  • GSAP: コンテンツアニメーション(ヒーローセクション、インフォグラフィック)

パターン 2:インタラクション種別による分離

  • Motion: ホバー、クリック、フォーカスなどの UI フィードバック
  • GSAP: スクロール連動、タイムラインベースのストーリーテリング

パターン 3:レスポンシブ対応による分離

  • Motion: モバイルのシンプルアニメーション
  • GSAP: デスクトップの複雑なアニメーション

以下の図は、推奨併用パターンの構造を示しています。

mermaidflowchart LR
    subgraph ui["UI 層(Motion)"]
        button["ボタン<br/>ホバー効果"]
        modal["モーダル<br/>表示/非表示"]
        nav["ナビゲーション<br/>遷移"]
    end

    subgraph content["コンテンツ層(GSAP)"]
        hero["ヒーロー<br/>セクション"]
        scroll["スクロール<br/>連動"]
        timeline["タイムライン<br/>制御"]
    end

    app["React アプリケーション"]

    ui --> app
    content --> app

    style ui fill:#0055ff,color:#fff
    style content fill:#88ce02,color:#000

図で理解できる要点:

  • UI 層とコンテンツ層で明確に役割を分担します
  • 各層は独立して動作し、保守性が向上します
  • 両ライブラリの強みを最大限に活かせる構造です

置換の判断基準

既存のライブラリを置き換える際は、以下の基準で判断しましょう。

# Motion から GSAP への置換を検討すべきケース

  1. パフォーマンス問題の顕在化

    • 60fps を維持できない
    • アニメーション要素が 50 個を超える
    • モバイルデバイスで動作が重い
  2. 機能的制約に直面

    • 複雑なタイムライン制御が必要
    • SVG 描画アニメーションが中心
    • スクロール連動の高度な制御が必要
  3. 他フレームワークへの移行予定

    • Next.js から別のフレームワークへの移行
    • React 以外の技術スタックとの統合

# GSAP から Motion への置換を検討すべきケース

  1. 開発効率の問題

    • チームメンバーの GSAP 習熟度が低い
    • React のライフサイクルとの統合が煩雑
    • TypeScript の型サポートを強化したい
  2. アニメーションの単純化

    • 複雑なタイムライン制御が不要になった
    • UI フィードバック中心のアニメーション
    • レイアウトアニメーションが主要な要件
  3. バンドルサイズの最適化

    • GSAP のプラグインを多数使用している
    • 実際には単純なアニメーションのみ
    • 初期読み込み速度の改善が急務

具体例

具体例 1:E コマースサイトのアニメーション設計

大規模な E コマースサイトで、Motion と GSAP を併用した実装例を見ていきましょう。

# プロジェクト概要

  • ページ数: 100 ページ以上
  • 商品数: 10,000 点以上
  • 主要アニメーション: 商品一覧、カート、チェックアウト、ヒーローセクション

# ライブラリ選択の方針

以下の表は、ページ種別ごとのライブラリ選択を示しています。

#ページ/機能アニメーション内容要素数選択ライブラリ選択理由
1トップページヒーローセクション、スクロール連動20-30 個GSAPScrollTrigger 必須
2商品一覧カードホバー、フィルタリング100 個以上GSAP大量要素の効率的制御
3商品詳細画像ギャラリー、モーダル10-15 個MotionUI インタラクション中心
4カート追加/削除アニメーション5-20 個Motionレイアウトアニメーション
5チェックアウトステップ遷移、バリデーション10 個以下Motionフォーム UI 連動

# 実装例:トップページのヒーローセクション(GSAP)

トップページでは、複雑なスクロール連動アニメーションを実装するため、GSAP を採用します。

まず、必要なパッケージをインストールしましょう。

bashyarn add gsap

次に、GSAP の ScrollTrigger プラグインを使用した実装です。

typescript// components/HeroSection.tsx
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

// ScrollTriggerプラグインを登録
gsap.registerPlugin(ScrollTrigger);

コンポーネントの型定義を行います。

typescriptinterface HeroSectionProps {
  title: string;
  subtitle: string;
  imageUrl: string;
}

ヒーローセクションのコンポーネント本体を実装します。

typescriptexport const HeroSection: React.FC<HeroSectionProps> = ({
  title,
  subtitle,
  imageUrl,
}) => {
  // アニメーション対象の要素への参照
  const heroRef = useRef<HTMLDivElement>(null);
  const titleRef = useRef<HTMLHeadingElement>(null);
  const subtitleRef = useRef<HTMLParagraphElement>(null);
  const imageRef = useRef<HTMLDivElement>(null);

  return (
    <div ref={heroRef} className='hero-section'>
      <h1 ref={titleRef}>{title}</h1>
      <p ref={subtitleRef}>{subtitle}</p>
      <div ref={imageRef} className='hero-image'>
        <img src={imageUrl} alt={title} />
      </div>
    </div>
  );
};

useEffect フック内で GSAP のアニメーションを定義します。

typescriptuseEffect(() => {
  // コンテキストを作成し、クリーンアップを容易にする
  const ctx = gsap.context(() => {
    // タイムラインを作成(複数のアニメーションを連携)
    const tl = gsap.timeline({
      scrollTrigger: {
        trigger: heroRef.current,
        start: 'top top', // ビューポート上部に要素上部が到達
        end: 'bottom top', // ビューポート上部に要素下部が到達
        scrub: 1, // スクロールに同期(値が大きいほど遅延)
        pin: true, // スクロール中に要素を固定
      },
    });

    // タイトルのアニメーション
    tl.from(titleRef.current, {
      y: 100, // 下から100px
      opacity: 0,
      duration: 1,
    });

    // サブタイトルのアニメーション(タイトルの0.5秒後に開始)
    tl.from(
      subtitleRef.current,
      {
        y: 50,
        opacity: 0,
        duration: 0.8,
      },
      '-=0.5' // 前のアニメーションの0.5秒前から開始
    );

    // 画像のアニメーション(スケール + パララックス)
    tl.from(
      imageRef.current,
      {
        scale: 1.2,
        y: 200,
        duration: 1.5,
      },
      '-=0.8'
    );
  }, heroRef);

  // クリーンアップ関数
  return () => ctx.revert();
}, []);

このコードでは、GSAP のタイムライン機能と ScrollTrigger プラグインを組み合わせています。 複雑なスクロール連動アニメーションを、わずか 50 行程度のコードで実現できますね。

# 実装例:商品詳細のモーダル(Motion)

商品詳細ページでは、画像拡大モーダルなどの UI インタラクションに Motion を使用します。

まず、Motion パッケージをインストールします。

bashyarn add motion

モーダルコンポーネントの型定義です。

typescript// components/ImageModal.tsx
import { motion, AnimatePresence } from 'motion/react';
import { useState } from 'react';

interface ImageModalProps {
  imageUrl: string;
  thumbnailUrl: string;
  alt: string;
}

モーダルの表示/非表示を管理するコンポーネントを実装します。

typescriptexport const ImageModal: React.FC<ImageModalProps> = ({
  imageUrl,
  thumbnailUrl,
  alt,
}) => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      {/* サムネイル(クリックでモーダル表示) */}
      <motion.img
        src={thumbnailUrl}
        alt={alt}
        onClick={() => setIsOpen(true)}
        whileHover={{ scale: 1.05 }} // ホバー時の拡大
        whileTap={{ scale: 0.95 }} // タップ時の縮小
        style={{ cursor: 'pointer' }}
      />

      {/* モーダルのアニメーション */}
      <AnimatePresence>
        {isOpen && (
          <ModalOverlay onClose={() => setIsOpen(false)}>
            <ModalContent imageUrl={imageUrl} alt={alt} />
          </ModalOverlay>
        )}
      </AnimatePresence>
    </>
  );
};

モーダルのオーバーレイコンポーネントを実装します。

typescript// オーバーレイの型定義
interface ModalOverlayProps {
  children: React.ReactNode;
  onClose: () => void;
}

const ModalOverlay: React.FC<ModalOverlayProps> = ({
  children,
  onClose,
}) => {
  return (
    <motion.div
      // 背景のフェードイン/アウト
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      onClick={onClose}
      style={{
        position: 'fixed',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'rgba(0, 0, 0, 0.8)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        zIndex: 1000,
      }}
    >
      {children}
    </motion.div>
  );
};

モーダルのコンテンツコンポーネントを実装します。

typescript// コンテンツの型定義
interface ModalContentProps {
  imageUrl: string;
  alt: string;
}

const ModalContent: React.FC<ModalContentProps> = ({
  imageUrl,
  alt,
}) => {
  return (
    <motion.div
      // モーダルのスケールアニメーション
      initial={{ scale: 0.8, opacity: 0 }}
      animate={{ scale: 1, opacity: 1 }}
      exit={{ scale: 0.8, opacity: 0 }}
      transition={{
        type: 'spring', // スプリングアニメーション
        damping: 25, // 減衰率高いほど早く停止stiffness: 300, // 剛性高いほど素早い動き)
      }}
      onClick={(e) => e.stopPropagation()} // クリックイベントの伝播を停止
      style={{
        maxWidth: '90vw',
        maxHeight: '90vh',
      }}
    >
      <img
        src={imageUrl}
        alt={alt}
        style={{ width: '100%', height: 'auto' }}
      />
    </motion.div>
  );
};

Motion のAnimatePresenceを使うことで、DOM 要素のマウント/アンマウント時のアニメーションを自動的に処理できます。 また、whileHoverwhileTapといった宣言的な API により、インタラクティブな UI フィードバックを簡単に実装できますね。

具体例 2:ダッシュボードアプリケーションの置換判断

既存の Motion ベースのダッシュボードで、パフォーマンス問題が発生したケースを見てましょう。

# 問題の発生

  • チャート要素: 200 個以上の SVG パス
  • リアルタイム更新: 1 秒ごとにデータ更新
  • 症状: 30fps 以下に低下、UI がカクつく

# パフォーマンス分析

React DevTools Profiler で計測した結果、アニメーション処理に 80%以上の CPU 時間を消費していました。

以下は、問題の原因を示した図です。

mermaidsequenceDiagram
    participant Data as データ更新
    participant React as React Re-render
    participant Motion as Motion アニメ
    participant DOM as DOM更新

    Data->>React: setState(1秒ごと)
    React->>React: 仮想DOM diff
    React->>Motion: props 変更検知
    Motion->>Motion: アニメーション計算
    Motion->>DOM: スタイル更新(200要素)

    Note over React,DOM: 処理時間: 約33ms<br/>(30fps相当)

    Data->>React: 次の更新
    Note over Data,DOM: フレーム落ち発生

図の要点:

  • 1 秒ごとのデータ更新が React の再レンダリングをトリガーします
  • Motion は 200 個の要素すべてでアニメーション計算を実行します
  • 処理時間が 33ms を超え、フレームレートが低下しています

# GSAP への置換判断

以下の基準により、GSAP への置換を決定しました。

typescript// 置換判断チェックリスト
const replacementCriteria = {
  // パフォーマンス要件
  targetFPS: 60, // 必須フレームレート
  currentFPS: 28, // 現在のフレームレート
  elementCount: 200, // アニメーション要素数
  updateFrequency: 1000, // 更新頻度(ms)

  // Motion の制約
  isLayoutAnimation: false, // レイアウトアニメーション不要
  needsComponentLifecycle: false, // ライフサイクル連動不要

  // GSAP の利点
  directDOMManipulation: true, // DOM直接操作が有効
  needsTimelineControl: true, // タイムライン制御が必要
  needsHighPerformance: true, // 高パフォーマンス必須

  // 判定結果
  shouldReplace: true, // 置換すべき
};

# GSAP 置換後の実装

チャートコンポーネントを GSAP で再実装します。

typescript// components/Chart.tsx(GSAP版)
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';

interface ChartProps {
  data: number[];
  updateInterval?: number;
}

useRef で SVG 要素への参照を保持します。

typescriptexport const Chart: React.FC<ChartProps> = ({
  data,
  updateInterval = 1000,
}) => {
  const svgRef = useRef<SVGSVGElement>(null);
  const pathsRef = useRef<SVGPathElement[]>([]);

  return (
    <svg ref={svgRef} width='800' height='400'>
      {data.map((value, index) => (
        <path
          key={index}
          ref={(el) => {
            if (el) pathsRef.current[index] = el;
          }}
          d={generatePathData(value, index)}
          fill='none'
          stroke='#0055ff'
          strokeWidth='2'
        />
      ))}
    </svg>
  );
};

GSAP でアニメーションを制御します。React のレンダリングサイクルをバイパスするのがポイントです。

typescriptuseEffect(() => {
  // GSAP のコンテキストを作成
  const ctx = gsap.context(() => {
    // 各パス要素に対してアニメーションを設定
    pathsRef.current.forEach((path, index) => {
      // データ更新時のアニメーション
      gsap.to(path, {
        attr: { d: generatePathData(data[index], index) }, // SVGのd属性を更新
        duration: 0.6, // アニメーション時間
        ease: 'power2.out', // イージング関数
        overwrite: 'auto', // 既存のアニメーションを上書き
      });
    });
  }, svgRef);

  return () => ctx.revert();
}, [data]); // dataが変更された時のみ実行

パフォーマンス最適化のための追加設定です。

typescriptuseEffect(() => {
  // GSAP のパフォーマンス最適化設定
  gsap.config({
    force3D: true, // GPU アクセラレーションを強制
    nullTargetWarn: false, // null ターゲットの警告を無効化
  });

  // requestAnimationFrame を使った効率的な更新
  gsap.ticker.fps(60); // 最大60fpsに制限
}, []);

# 置換後の結果

GSAP への置換により、以下の改善が得られました。

#指標Motion 使用時GSAP 使用時改善率
1フレームレート28fps60fps+114%
2CPU 使用率80%35%-56%
3メモリ使用量120MB85MB-29%
4初回レンダリング時間450ms180ms-60%
5アニメーション遅延120ms16ms-87%

この結果から、大量の要素を扱うアニメーションでは、GSAP の直接 DOM 操作が圧倒的に有利であることがわかります。

具体例 3:併用時の状態管理パターン

Motion と GSAP を併用する場合、状態管理の設計が重要になります。

# カスタムフックによる抽象化

アニメーション制御を統一的に扱うため、カスタムフックを作成しましょう。

typescript// hooks/useAnimationController.ts
import { useCallback, useRef } from 'react';
import { gsap } from 'gsap';

type AnimationLibrary = 'motion' | 'gsap';

interface AnimationConfig {
  library: AnimationLibrary;
  duration?: number;
  ease?: string;
}

アニメーションコントローラーのカスタムフックを実装します。

typescriptexport const useAnimationController = (
  config: AnimationConfig
) => {
  const elementRef = useRef<HTMLElement>(null);

  // アニメーション実行関数
  const animate = useCallback(
    (properties: Record<string, any>) => {
      if (!elementRef.current) return;

      if (config.library === 'gsap') {
        // GSAP を使用する場合
        gsap.to(elementRef.current, {
          ...properties,
          duration: config.duration || 0.5,
          ease: config.ease || 'power2.out',
        });
      }
      // Motion の場合は、コンポーネント側で motion.div を使用
    },
    [config]
  );

  return { elementRef, animate };
};

# 使用例:統一インターフェースでのアニメーション制御

カスタムフックを使用したコンポーネント実装です。

typescript// components/AnimatedCard.tsx
import { motion } from 'motion/react';
import { useAnimationController } from '../hooks/useAnimationController';

interface AnimatedCardProps {
  title: string;
  useGsap?: boolean; // GSAP使用フラグ
}

GSAP と Motion を切り替え可能なカードコンポーネントです。

typescriptexport const AnimatedCard: React.FC<AnimatedCardProps> = ({
  title,
  useGsap = false,
}) => {
  const { elementRef, animate } = useAnimationController({
    library: useGsap ? 'gsap' : 'motion',
    duration: 0.3,
  });

  // ホバー時のアニメーション
  const handleHover = () => {
    if (useGsap) {
      animate({ scale: 1.05, y: -5 });
    }
  };

  // ホバー解除時のアニメーション
  const handleHoverEnd = () => {
    if (useGsap) {
      animate({ scale: 1, y: 0 });
    }
  };

  // Motion を使用する場合
  if (!useGsap) {
    return (
      <motion.div
        whileHover={{ scale: 1.05, y: -5 }}
        transition={{ duration: 0.3 }}
        style={cardStyle}
      >
        <h3>{title}</h3>
      </motion.div>
    );
  }

  // GSAP を使用する場合
  return (
    <div
      ref={elementRef as any}
      onMouseEnter={handleHover}
      onMouseLeave={handleHoverEnd}
      style={cardStyle}
    >
      <h3>{title}</h3>
    </div>
  );
};

スタイル定義です。

typescriptconst cardStyle: React.CSSProperties = {
  padding: '20px',
  borderRadius: '8px',
  backgroundColor: '#fff',
  boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
  cursor: 'pointer',
};

このパターンにより、ライブラリの選択を柔軟に切り替えられ、将来的な置換も容易になります。

まとめ

Motion(旧 Framer Motion)と GSAP の併用・置換判断について、4 つの重要なポイントをお伝えしました。

判断の 4 つの軸

  1. アニメーション種別: コンポーネントライフサイクル連動なら Motion、複雑なタイムライン制御なら GSAP を選択しましょう
  2. パフォーマンス要件: 同時アニメーション要素が 50 個を超える場合は GSAP が有利です
  3. 開発効率と保守性: チームの習熟度とコードの可読性を重視してください
  4. バンドルサイズ: 初期読み込み速度を考慮し、必要な機能のみを導入しましょう

併用の推奨パターン

UI 層は Motion、コンテンツ層は GSAP という役割分担により、両ライブラリの強みを最大限に活かせます。 明確な基準を設けることで、チーム全体の開発効率と保守性が向上しますね。

置換の判断基準

パフォーマンス問題が顕在化した場合や、機能的制約に直面した場合は、置換を検討してください。 ただし、置換コストと得られる効果を慎重に比較し、段階的に移行することが重要です。

大規模アニメーション実装では、技術選定が長期的なプロジェクト成功を左右します。 本記事の判断基準を参考に、プロジェクトに最適なアプローチを見つけていただければ幸いです。

関連リンク