T-CREATOR

2025 年版 Motion(旧 Framer Motion) 徹底活用テクニック集

2025 年版 Motion(旧 Framer Motion) 徹底活用テクニック集

Motion は単なるアニメーションライブラリを超え、2025 年には Web アプリケーションの UX を革新する強力なツールへと進化しました。

この記事では、Motion v12 の最新機能から高度な実装テクニックまで、実際のプロダクション環境で差をつける実践的な技術を詳しく解説いたします。基礎的な使い方は他の記事にお任せし、ここではプロフェッショナルレベルの実装に挑戦してみましょう。

背景

Motion v12 の革新的変化

2025 年にリリースされた Motion v12 は、パフォーマンスと開発体験の両面で大幅な改善を実現しています。

主要な進化ポイント

機能v11 までv12 での改善
レンダリング性能DOM 操作中心Web Workers + GPU 最適化
バンドルサイズ32KB (gzipped)24KB (gzipped)
TypeScript サポート基本的な型定義完全な型安全性
React 18 対応限定的Concurrent Features 完全対応
開発ツール基本的なデバッグAdvanced DevTools 統合

特に注目すべきは、Independent Transformの導入により、複雑なアニメーションでも 60FPS を維持できるようになったことです。

現代の Web アプリケーションに求められる高度なアニメーション

現代のユーザーは、ネイティブアプリレベルの滑らかさとレスポンシブ性を Web アプリケーションにも期待しています。

求められる技術水準

  • マイクロインタラクション: 0.1 秒以下での即座反応
  • 複雑な状態遷移: 多段階のアニメーション連携
  • デバイス適応: タッチ、マウス、キーボード操作への最適化
  • アクセシビリティ: 前庭障害への配慮と設定対応

課題

複雑なアニメーション実装の技術的ハードル

高度なアニメーションを実装する際に直面する主要な課題を整理しましょう。

状態管理の複雑化 複数のアニメーション要素が相互作用する場合、状態管理が急激に複雑になります。

typescript// 問題のあるアプローチ
const [step1, setStep1] = useState(false);
const [step2, setStep2] = useState(false);
const [step3, setStep3] = useState(false);
const [isReversing, setIsReversing] = useState(false);

// これでは管理が困難
useEffect(() => {
  if (step1 && !isReversing) {
    setTimeout(() => setStep2(true), 300);
  }
  if (step2 && !isReversing) {
    setTimeout(() => setStep3(true), 600);
  }
  // さらに複雑になっていく...
}, [step1, step2, step3, isReversing]);

パフォーマンスのボトルネック 不適切な実装により、フレームドロップや応答性の低下が発生します。

パフォーマンスとリッチな表現の両立

美しいアニメーションを実装しつつ、パフォーマンスを維持することは常に課題となります。

メモリリークの回避 長時間動作するアニメーションでのメモリ管理が重要です。

バッテリー消費の最適化 モバイルデバイスでの効率的な動作が求められます。

解決策

2025 年版 Motion 活用戦略

現代的なアプローチで、これらの課題を効率的に解決できます。

状態駆動アーキテクチャ Variants パターンを活用した宣言的なアニメーション管理

パフォーマンスファースト設計 GPU 加速と効率的なレンダリング戦略

型安全性の確保 TypeScript を活用した堅牢な実装

高度なテクニックの体系的習得法

以下の順序で学習することで、効率的にスキルアップできます:

  1. Variants マスター: 複雑な状態管理の基盤
  2. Layout Animations: 動的レイアウト変更の制御
  3. Scroll Integration: スクロール連動の高度活用
  4. Gesture Handling: タッチ操作の完全制御
  5. Performance Optimization: 実用レベルの最適化

具体例

Layout Animations の完全攻略法

Layout Animations は Motion の最も強力な機能の一つです。動的なレイアウト変更を自動でアニメーションしてくれます。

基本的な Layout Animation

typescriptimport { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';

const AdvancedLayoutExample = () => {
  const [selectedId, setSelectedId] = useState<
    string | null
  >(null);
  const [items, setItems] = useState([
    { id: '1', title: 'カード 1', content: 'コンテンツ 1' },
    { id: '2', title: 'カード 2', content: 'コンテンツ 2' },
    { id: '3', title: 'カード 3', content: 'コンテンツ 3' },
  ]);

  return (
    <div className='grid-container'>
      <motion.div layout className='card-grid'>
        {items.map((item) => (
          <motion.div
            key={item.id}
            layoutId={item.id}
            className='card'
            onClick={() => setSelectedId(item.id)}
            whileHover={{ scale: 1.02 }}
            transition={{
              layout: { duration: 0.3, ease: 'easeOut' },
              scale: { duration: 0.2 },
            }}
          >
            <h3>{item.title}</h3>
          </motion.div>
        ))}
      </motion.div>

      <AnimatePresence>
        {selectedId && (
          <motion.div
            className='overlay'
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            onClick={() => setSelectedId(null)}
          >
            <motion.div
              layoutId={selectedId}
              className='expanded-card'
              onClick={(e) => e.stopPropagation()}
            >
              {items.find(
                (item) => item.id === selectedId
              ) && (
                <>
                  <h2>
                    {
                      items.find(
                        (item) => item.id === selectedId
                      )?.title
                    }
                  </h2>
                  <p>
                    {
                      items.find(
                        (item) => item.id === selectedId
                      )?.content
                    }
                  </p>
                  <button
                    onClick={() => setSelectedId(null)}
                  >
                    閉じる
                  </button>
                </>
              )}
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
};

高度な Layout 制御テクニック

typescriptimport { motion, LayoutGroup } from 'framer-motion';

const MultiGroupLayout = () => {
  const [activeTab, setActiveTab] = useState(0);

  return (
    <LayoutGroup>
      <motion.div className='tab-container'>
        {tabs.map((tab, index) => (
          <motion.button
            key={tab.id}
            className={`tab ${
              activeTab === index ? 'active' : ''
            }`}
            onClick={() => setActiveTab(index)}
            layout='position'
            transition={{
              type: 'spring',
              stiffness: 300,
              damping: 30,
            }}
          >
            {tab.label}
            {activeTab === index && (
              <motion.div
                className='tab-indicator'
                layoutId='activeTab'
                transition={{ type: 'spring', bounce: 0.3 }}
              />
            )}
          </motion.button>
        ))}
      </motion.div>

      <motion.div
        className='tab-content'
        layout
        transition={{ duration: 0.3 }}
      >
        <AnimatePresence mode='wait'>
          <motion.div
            key={activeTab}
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -20 }}
            transition={{ duration: 0.2 }}
          >
            {tabs[activeTab].content}
          </motion.div>
        </AnimatePresence>
      </motion.div>
    </LayoutGroup>
  );
};

よくあるエラーと解決法

bash# Error: AnimatePresence children must each have a unique key prop
# 原因: AnimatePresence内の要素にkeyが設定されていない
typescript// 間違った実装
<AnimatePresence>
  {showModal && (
    <motion.div>モーダル</motion.div> // keyが不足
  )}
</AnimatePresence>

// 正しい実装
<AnimatePresence>
  {showModal && (
    <motion.div key="modal">モーダル</motion.div> // keyを追加
  )}
</AnimatePresence>

Scroll-triggered Animations の高度活用

スクロール連動アニメーションの実装で、ユーザーエンゲージメントを大幅に向上させることができます。

useScroll フックの高度活用

typescriptimport {
  useScroll,
  useTransform,
  motion,
} from 'framer-motion';
import { useRef } from 'react';

const AdvancedScrollAnimation = () => {
  const containerRef = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ['start end', 'end start'],
  });

  // 複数の値を同時にアニメーション
  const y = useTransform(
    scrollYProgress,
    [0, 1],
    [100, -100]
  );
  const opacity = useTransform(
    scrollYProgress,
    [0, 0.3, 0.7, 1],
    [0, 1, 1, 0]
  );
  const scale = useTransform(
    scrollYProgress,
    [0, 0.5, 1],
    [0.8, 1, 1.2]
  );
  const rotate = useTransform(
    scrollYProgress,
    [0, 1],
    [0, 360]
  );

  return (
    <div ref={containerRef} className='scroll-container'>
      <motion.div
        style={{ y, opacity, scale, rotate }}
        className='scroll-target'
      >
        <h2>スクロールで変化する要素</h2>
      </motion.div>
    </div>
  );
};

パララックス効果の実装

typescriptconst ParallaxSection = () => {
  const { scrollY } = useScroll();

  // 異なる速度でスクロール
  const backgroundY = useTransform(
    scrollY,
    [0, 1000],
    [0, -300]
  );
  const middleY = useTransform(
    scrollY,
    [0, 1000],
    [0, -150]
  );
  const foregroundY = useTransform(
    scrollY,
    [0, 1000],
    [0, -50]
  );

  return (
    <div className='parallax-container'>
      <motion.div
        className='parallax-layer background'
        style={{ y: backgroundY }}
      />
      <motion.div
        className='parallax-layer middle'
        style={{ y: middleY }}
      />
      <motion.div
        className='parallax-layer foreground'
        style={{ y: foregroundY }}
      />
    </div>
  );
};

Gesture Integration とカスタムコントロール

タッチジェスチャーとの高度な連携により、ネイティブアプリライクな操作感を実現できます。

bashyarn add framer-motion @use-gesture/react

マルチタッチ対応ドラッグ

typescriptimport {
  motion,
  useMotionValue,
  useTransform,
} from 'framer-motion';
import { useDrag } from '@use-gesture/react';

const AdvancedDragComponent = () => {
  const x = useMotionValue(0);
  const y = useMotionValue(0);

  // ドラッグ距離に基づく視覚効果
  const rotateX = useTransform(y, [-200, 200], [30, -30]);
  const rotateY = useTransform(x, [-200, 200], [-30, 30]);
  const scale = useTransform(
    x,
    [-200, 0, 200],
    [0.8, 1, 0.8]
  );

  const bind = useDrag({
    bounds: {
      left: -200,
      right: 200,
      top: -200,
      bottom: 200,
    },
    rubberband: true,
    onDrag: ({
      movement: [mx, my],
      velocity: [vx, vy],
    }) => {
      x.set(mx);
      y.set(my);

      // 速度に基づく追加効果
      if (Math.abs(vx) > 500 || Math.abs(vy) > 500) {
        // 高速ドラッグ時の特別な処理
        console.log('Fast drag detected');
      }
    },
    onDragEnd: ({ velocity: [vx, vy] }) => {
      // 慣性に基づく追加アニメーション
      const finalX = x.get() + vx * 0.1;
      const finalY = y.get() + vy * 0.1;

      // 境界チェック付きで最終位置を設定
      x.set(Math.max(-200, Math.min(200, finalX)));
      y.set(Math.max(-200, Math.min(200, finalY)));
    },
  });

  return (
    <motion.div
      {...bind()}
      style={{
        x,
        y,
        rotateX,
        rotateY,
        scale,
        touchAction: 'none',
      }}
      className='draggable-advanced'
    >
      高度なドラッグ操作
    </motion.div>
  );
};

カスタムジェスチャーの実装

typescriptconst SwipeToActionCard = () => {
  const [currentIndex, setCurrentIndex] = useState(0);
  const constraintsRef = useRef<HTMLDivElement>(null);

  const bind = useDrag({
    axis: 'x',
    onDragEnd: ({ movement: [mx], velocity: [vx] }) => {
      const threshold = 100;
      const velocityThreshold = 500;

      if (mx > threshold || vx > velocityThreshold) {
        // 右スワイプ
        setCurrentIndex((prev) => Math.max(0, prev - 1));
      } else if (
        mx < -threshold ||
        vx < -velocityThreshold
      ) {
        // 左スワイプ
        setCurrentIndex((prev) =>
          Math.min(items.length - 1, prev + 1)
        );
      }
    },
  });

  return (
    <div ref={constraintsRef} className='swipe-container'>
      {items.map((item, index) => (
        <motion.div
          key={item.id}
          {...bind()}
          className='swipe-card'
          animate={{
            x: (index - currentIndex) * 300,
            scale: index === currentIndex ? 1 : 0.9,
            opacity:
              Math.abs(index - currentIndex) <= 1 ? 1 : 0.3,
          }}
          transition={{
            type: 'spring',
            stiffness: 300,
            damping: 30,
          }}
        >
          {item.content}
        </motion.div>
      ))}
    </div>
  );
};

Variants パターンによる複雑な状態管理

Variants を使うことで、複雑なアニメーション状態を宣言的に管理できます。

typescriptconst complexVariants = {
  initial: {
    opacity: 0,
    scale: 0.8,
    y: 50,
  },
  visible: {
    opacity: 1,
    scale: 1,
    y: 0,
    transition: {
      duration: 0.6,
      ease: 'easeOut',
      staggerChildren: 0.1,
    },
  },
  hover: {
    scale: 1.05,
    transition: { duration: 0.2 },
  },
  tap: {
    scale: 0.95,
    transition: { duration: 0.1 },
  },
  exit: {
    opacity: 0,
    scale: 0.8,
    y: -50,
    transition: {
      duration: 0.3,
      ease: 'easeIn',
    },
  },
};

const childVariants = {
  initial: { opacity: 0, x: -20 },
  visible: {
    opacity: 1,
    x: 0,
    transition: { duration: 0.4 },
  },
};

const ComplexVariantsExample = () => {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <AnimatePresence>
      {isVisible && (
        <motion.div
          variants={complexVariants}
          initial='initial'
          animate='visible'
          whileHover='hover'
          whileTap='tap'
          exit='exit'
          className='complex-container'
        >
          <motion.h2 variants={childVariants}>
            タイトル
          </motion.h2>
          <motion.p variants={childVariants}>
            説明文
          </motion.p>
          <motion.button variants={childVariants}>
            アクションボタン
          </motion.button>
        </motion.div>
      )}
    </AnimatePresence>
  );
};

動的 Variants の活用

typescriptconst createDynamicVariants = (itemCount: number) => ({
  hidden: {
    opacity: 0,
  },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1,
      delayChildren: 0.3,
      when: 'beforeChildren',
    },
  },
});

const itemVariants = {
  hidden: {
    opacity: 0,
    y: 20,
    rotateX: -90,
  },
  visible: {
    opacity: 1,
    y: 0,
    rotateX: 0,
    transition: {
      type: 'spring',
      stiffness: 100,
    },
  },
};

よくあるエラーと解決法

bash# Error: Variants with name "myVariant" does not exist
# 原因: variantsオブジェクトに存在しない名前を指定
typescript// 問題のあるコード
const variants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 }
};

<motion.div
  variants={variants}
  animate="show" // 存在しないvariant名
/>

// 修正されたコード
<motion.div
  variants={variants}
  animate="visible" // 正しいvariant名
/>

まとめ

Motion 上級者への道筋

この記事で紹介した高度なテクニックを習得することで、以下のスキルが身につきます:

技術的な成長

  • 複雑なアニメーション状態の効率的な管理
  • パフォーマンスを意識した実装設計
  • TypeScript との組み合わせによる型安全な開発

実践的な応用力

  • プロダクション品質のアニメーション実装
  • ユーザー体験を大幅に向上させる表現技法
  • モダンなフロントエンド開発手法との統合

次のステップとして推奨する学習項目

  • React Server Components との組み合わせ
  • Next.js App Router での最適化
  • WebGL/Three.js との高度な連携
  • アクセシビリティを考慮した実装

継続的な実践と実験を通じて、Motion の真の力を引き出してください。2025 年の Web 開発において、このスキルは確実にあなたの強力な武器となるはずです。

関連リンク