T-CREATOR

Motion(旧 Framer Motion)入門:React アニメーションを最速で始める

Motion(旧 Framer Motion)入門:React アニメーションを最速で始める

React でアニメーションを実装する際、CSS だけでは表現しきれない複雑な動きに悩まれたことはありませんか。また、コンポーネントの状態変化に応じて滑らかなアニメーションを作りたいけれど、実装が複雑すぎて諦めてしまった経験もあるでしょう。

そんな課題を解決してくれるのが Motion です。Motion は React のためのアニメーションライブラリで、簡単な記述で美しく滑らかなアニメーションを実装できます。この記事では、Motion の基本から実践的な使い方まで、初心者の方にもわかりやすく解説していきます。

Motion とは何か

Motion は React 専用に設計されたアニメーションライブラリです。以前は Framer Motion という名前で親しまれていましたが、現在は Motion として新しくリブランドされています。React コンポーネントに直感的で美しいアニメーションを追加できる強力なツールとして、多くの開発者に愛用されています。

Framer Motion から Motion への変更点

2024 年、Framer Motion は Motion として新しく生まれ変わりました。名前の変更だけでなく、パッケージの構造や API も改良されています。

主な変更点は以下の通りです:

#変更項目Framer MotionMotion
1パッケージ名framer-motionmotion
2バンドルサイズやや大きめ最適化済み
3TypeScript サポート標準対応より強化
4パフォーマンス高性能さらに最適化

従来の Framer Motion をお使いの方も、移行は比較的簡単です。API の大部分は互換性があり、主にインポート文の変更程度で済みます。

React アニメーションライブラリとしての位置づけ

Motion は React エコシステムにおいて、アニメーションライブラリのデファクトスタンダードと言える存在です。

React アニメーションライブラリの比較を見てみましょう:

#ライブラリ特徴適用場面
1Motion宣言的、高機能複雑なアニメーション
2React Transition Group基本的、軽量シンプルな遷移
3React Spring物理ベースリアルな動き
4Lottie ReactAfter Effects 連携複雑なイラストアニメーション

Motion が選ばれる理由は、React の思想に沿った宣言的な記述方法と、豊富な機能性のバランスにあります。

基本的な課題

React でアニメーションを実装する際、開発者が直面する課題について詳しく見ていきましょう。

従来の CSS アニメーションの限界

CSS アニメーションは手軽に使える反面、React アプリケーションでは以下のような制約があります。

CSS だけでは困難な制御

typescript// React コンポーネントの状態に応じた動的なアニメーション
const [isExpanded, setIsExpanded] = useState(false);

// CSS だけでは以下のような複雑な制御が難しい
// - 状態に応じた異なるアニメーション
// - 動的な値(props)を使ったアニメーション
// - アニメーション完了時のコールバック

この例では、単純な状態変更でも CSS だけでは表現が困難な場面を示しています。

タイミング制御の複雑さ

css/* CSS でのアニメーション定義は静的 */
.fade-in {
  animation: fadeIn 0.3s ease-in-out;
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* 動的な値やタイミングの変更が困難 */

CSS アニメーションは事前定義された動きしか実現できません。ユーザーの操作や API レスポンスに応じて動的に変化するアニメーションには不向きです。

React での状態変化に伴うアニメーションの難しさ

React の状態管理とアニメーションの組み合わせには独特の困難さがあります。

以下の図は、React での状態変化とアニメーションのタイミング問題を示しています:

mermaidsequenceDiagram
  participant User
  participant Component
  participant DOM

  User->>Component: クリック
  Component->>Component: setState
  Component->>DOM: 再レンダリング
  Note over DOM: アニメーション途中で<br/>DOM が更新される
  DOM->>User: ちらつき発生

この図が示すように、状態変更と同時に DOM が更新されるため、アニメーションが中断されたり不自然な動きになることがあります。

コンポーネントのマウント・アンマウント問題

typescriptconst ConditionalComponent = ({
  show,
}: {
  show: boolean;
}) => {
  return (
    <>
      {show && (
        <div className='fade-in'>
          {/* show が false になると即座に DOM から削除される */}
          {/* アニメーション途中でも容赦なく削除される */}
        </div>
      )}
    </>
  );
};

React の条件付きレンダリングでは、コンポーネントが即座にマウント・アンマウントされるため、フェードアウトアニメーションを自然に実装するのが困難です。

Motion で解決できること

Motion は上記の課題を包括的に解決する、React に最適化されたアニメーションライブラリです。

宣言的アニメーション

Motion の最大の特徴は、アニメーションを宣言的に記述できることです。

以下は Motion を使った基本的なアニメーションの例です:

typescriptimport { motion } from 'motion/react';

const AnimatedComponent = () => {
  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.8 }}
      animate={{ opacity: 1, scale: 1 }}
      transition={{ duration: 0.3 }}
    >
      Hello Motion!
    </motion.div>
  );
};

この短いコードで、フェードインとスケールアニメーションを同時に実現できます。CSS のように別ファイルでキーフレームを定義する必要もありません。

動的な値との連携

typescriptconst DynamicAnimation = ({ color, size }: Props) => {
  return (
    <motion.div
      animate={{
        backgroundColor: color, // props から動的に設定
        width: size, // 値が変わると自動でアニメーション
        height: size,
      }}
      transition={{ duration: 0.5 }}
    >
      Dynamic content
    </motion.div>
  );
};

props や state の変更に応じて、自動的にアニメーションが適用されます。これにより、複雑な状態管理アプリケーションでも自然なアニメーションを実現できます。

React との完璧な統合

Motion は React のライフサイクルと完全に統合されており、以下のような問題を解決します:

条件付きレンダリングでのアニメーション制御

typescriptimport { AnimatePresence } from 'motion/react';

const ConditionalAnimation = ({
  show,
}: {
  show: boolean;
}) => {
  return (
    <AnimatePresence>
      {show && (
        <motion.div
          initial={{ opacity: 0, y: -20 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: 20 }} // アンマウント時のアニメーション
          transition={{ duration: 0.2 }}
        >
          アンマウント時もアニメーション!
        </motion.div>
      )}
    </AnimatePresence>
  );
};

AnimatePresence コンポーネントを使うことで、コンポーネントがアンマウントされる際も美しいアニメーションを実現できます。

豊富な機能とパフォーマンス

Motion は高度なアニメーション機能を提供しながら、パフォーマンスも最適化されています。

Motion の主要機能一覧:

#機能カテゴリ具体的な機能用途例
1基本アニメーションopacity, scale, positionフェード、拡大縮小
2レイアウトアニメーションlayout プロパティサイズ変更の滑らかな遷移
3ジェスチャーdrag, hover, tapインタラクティブ UI
4SVG アニメーションpath, strokeアイコンアニメーション
5スクロール連動useScroll, useTransformパララックス効果

以下の図は Motion のアニメーション処理フローを示しています:

mermaidflowchart TD
  A[React State 変更] --> B{Motion コンポーネント}
  B --> C[アニメーション計算]
  C --> D[60fps での更新]
  D --> E[GPU 最適化された描画]
  E --> F[滑らかな視覚効果]

  B --> G[イベントハンドラ]
  G --> H[ジェスチャー認識]
  H --> A

Motion は内部で高度な最適化を行っており、開発者が複雑な計算を意識することなく、滑らかなアニメーションを実現できます。

環境構築と基本セットアップ

Motion を使い始めるための環境構築から説明していきます。既存の React プロジェクトに簡単に導入できる点も Motion の魅力の一つです。

インストール方法

Motion のインストールは Yarn を使って行います。

bash# Motion パッケージのインストール
yarn add motion

このコマンドで Motion の最新版がプロジェクトに追加されます。依存関係も自動的に解決され、すぐに使い始めることができます。

TypeScript プロジェクトでの追加設定

TypeScript を使用している場合、型定義は既に含まれているため、追加の設定は不要です。

typescript// tsconfig.json の設定例(必要に応じて)
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true
  }
}

Motion は TypeScript ファーストで設計されており、強力な型サポートを提供しています。

基本的な設定

Motion を使うための基本的な設定とインポート方法を確認しましょう。

基本的なインポート

typescriptimport { motion } from 'motion/react';

// 必要に応じて追加のインポート
import {
  AnimatePresence,
  useAnimation,
} from 'motion/react';

Motion の主要な機能は motion​/​react からインポートできます。基本的なアニメーションなら motion だけで十分です。

最初のアニメーション実装

最も簡単な Motion アニメーションを実装してみましょう。

typescriptimport React from 'react';
import { motion } from 'motion/react';

const FirstAnimation = () => {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 1 }}
    >
      Welcome to Motion!
    </motion.div>
  );
};

export default FirstAnimation;

この例では、ページ読み込み時にテキストがフェードインします。motion.div は通常の div 要素にアニメーション機能を追加したものです。

基本的なアニメーション実装

Motion の基本的なアニメーションパターンを実際に実装してみましょう。実用的な例から学ぶことで、すぐに応用できる知識が身につきます。

フェードイン・フェードアウト

最も基本的なアニメーションであるフェード効果の実装方法です。

基本的なフェードイン

typescriptimport React, { useState } from 'react';
import { motion } from 'motion/react';

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

  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>
        トグル
      </button>

      {isVisible && (
        <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          transition={{ duration: 0.3 }}
          style={{
            marginTop: '20px',
            padding: '20px',
            background: '#f0f0f0',
          }}
        >
          フェードイン・アウトする要素
        </motion.div>
      )}
    </div>
  );
};

この実装では、ボタンクリックで要素の表示・非表示が切り替わり、その際にフェードアニメーションが適用されます。

AnimatePresence を使った改良版

typescriptimport { AnimatePresence, motion } from 'motion/react';

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

  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>
        {isVisible ? '非表示' : '表示'}
      </button>

      <AnimatePresence>
        {isVisible && (
          <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.3 }}
          >
            AnimatePresence で制御されたフェード
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
};

AnimatePresence を使うことで、コンポーネントがアンマウントされる際のアニメーションも適切に制御できます。

スケールアニメーション

要素の拡大・縮小アニメーションは、ユーザーの注意を引くのに効果的です。

ホバー時のスケール効果

typescriptconst ScaleOnHover = () => {
  return (
    <motion.button
      whileHover={{ scale: 1.1 }}
      whileTap={{ scale: 0.95 }}
      transition={{ type: 'spring', stiffness: 300 }}
      style={{
        padding: '12px 24px',
        backgroundColor: '#007acc',
        color: 'white',
        border: 'none',
        borderRadius: '8px',
        cursor: 'pointer',
      }}
    >
      ホバーでスケール
    </motion.button>
  );
};

whileHoverwhileTap を使うことで、マウスの状態に応じたインタラクティブなアニメーションを簡単に実装できます。

カードのエントランスアニメーション

typescriptconst CardEntrance = ({ children, delay = 0 }) => {
  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.8, y: 20 }}
      animate={{ opacity: 1, scale: 1, y: 0 }}
      transition={{
        duration: 0.4,
        delay,
        type: 'spring',
        stiffness: 100,
      }}
      style={{
        padding: '20px',
        margin: '10px',
        backgroundColor: 'white',
        borderRadius: '12px',
        boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
      }}
    >
      {children}
    </motion.div>
  );
};

この例では、カードが下から上に浮き上がりながらフェードインするエレガントなエントランスアニメーションを実現しています。

位置移動アニメーション

要素の移動アニメーションは、UI の流れを明確にし、ユーザーの理解を助けます。

スライドイン効果

typescriptconst SlideInAnimation = ({ direction = 'left' }) => {
  const getInitialPosition = () => {
    switch (direction) {
      case 'left':
        return { x: -100, opacity: 0 };
      case 'right':
        return { x: 100, opacity: 0 };
      case 'top':
        return { y: -50, opacity: 0 };
      case 'bottom':
        return { y: 50, opacity: 0 };
      default:
        return { x: -100, opacity: 0 };
    }
  };

  return (
    <motion.div
      initial={getInitialPosition()}
      animate={{ x: 0, y: 0, opacity: 1 }}
      transition={{ duration: 0.4, ease: 'easeOut' }}
    >
      {direction} からスライドイン
    </motion.div>
  );
};

方向を指定してスライドインアニメーションを実装できる再利用可能なコンポーネントです。

ドラッグ可能な要素

typescriptconst DraggableElement = () => {
  return (
    <motion.div
      drag
      dragConstraints={{
        left: -100,
        right: 100,
        top: -100,
        bottom: 100,
      }}
      dragElastic={0.2}
      whileDrag={{ scale: 1.1, cursor: 'grabbing' }}
      style={{
        width: '100px',
        height: '100px',
        backgroundColor: '#ff6b6b',
        borderRadius: '12px',
        cursor: 'grab',
      }}
    >
      ドラッグできます
    </motion.div>
  );
};

drag プロパティを追加するだけで、要素をドラッグ可能にできます。dragConstraints で移動範囲を制限し、whileDrag でドラッグ中の見た目を変更できます。

Motion のアニメーション実装パターンを整理すると以下のようになります:

#アニメーション種類主な用途実装の難易度
1フェード要素の表示・非表示
2スケールボタンのフィードバック
3位置移動ページ遷移、スライド
4レイアウトアニメーション動的サイズ変更
5ジェスチャードラッグ、スワイプやや難

レスポンシブ対応とベストプラクティス

Motion を実際のプロジェクトで使用する際の実践的な考慮事項とベストプラクティスについて説明します。

デバイス別最適化

モバイルデバイスでのパフォーマンスを考慮した実装方法です。

reduced-motion への配慮

typescriptimport { motion, useReducedMotion } from 'motion/react';

const AccessibleAnimation = ({ children }) => {
  const shouldReduceMotion = useReducedMotion();

  return (
    <motion.div
      animate={{
        opacity: 1,
        x: shouldReduceMotion ? 0 : 100, // アニメーション無効時は移動なし
      }}
      transition={{
        duration: shouldReduceMotion ? 0 : 0.5, // アニメーション無効時は即座に
      }}
    >
      {children}
    </motion.div>
  );
};

ユーザーのシステム設定でアニメーションを減らす設定にしている場合、適切に対応することでアクセシビリティが向上します。

デバイス別のアニメーション調整

typescriptconst ResponsiveAnimation = () => {
  const isMobile = window.innerWidth < 768;

  const animationVariants = {
    hidden: {
      opacity: 0,
      y: isMobile ? 20 : 50, // モバイルでは移動量を控えめに
    },
    visible: {
      opacity: 1,
      y: 0,
    },
  };

  return (
    <motion.div
      initial='hidden'
      animate='visible'
      variants={animationVariants}
      transition={{
        duration: isMobile ? 0.2 : 0.4, // モバイルでは高速化
      }}
    >
      レスポンシブアニメーション
    </motion.div>
  );
};

デバイスの特性に応じてアニメーションの強度や速度を調整することで、最適な体験を提供できます。

パフォーマンス考慮事項

Motion を使用する際の重要なパフォーマンス最適化ポイントです。

will-change プロパティの自動最適化

typescriptconst OptimizedAnimation = () => {
  return (
    <motion.div
      animate={{ x: 100 }}
      transition={{ duration: 2 }}
      // Motion が自動的に will-change を適用
      // GPU レイヤーでの描画が最適化される
    >
      最適化されたアニメーション
    </motion.div>
  );
};

Motion は自動的に will-change CSS プロパティを適用し、GPU での描画を最適化します。開発者が手動で設定する必要はありません。

大量要素のアニメーション最適化

typescriptconst ListAnimation = ({ items }: { items: string[] }) => {
  return (
    <div>
      {items.map((item, index) => (
        <motion.div
          key={item}
          initial={{ opacity: 0, x: -20 }}
          animate={{ opacity: 1, x: 0 }}
          transition={{
            delay: index * 0.1, // 段階的なアニメーション
            duration: 0.3,
          }}
          layout // レイアウト変更時の自動アニメーション
        >
          {item}
        </motion.div>
      ))}
    </div>
  );
};

大量の要素をアニメーションする際は、遅延を使った段階的な表示で視覚的なインパクトを演出しながら、パフォーマンスも保てます。

Motion のパフォーマンス最適化における処理の流れを図で示すと:

mermaidflowchart LR
  A[アニメーション開始] --> B{GPU 対応確認}
  B -->|対応済み| C[GPU レイヤー利用]
  B -->|未対応| D[CPU フォールバック]
  C --> E[60fps での描画]
  D --> F[最適化された CPU 描画]
  E --> G[滑らかなアニメーション]
  F --> G

Motion は環境に応じて最適な描画方法を自動選択し、常に最高のパフォーマンスを維持します。

メモリリークの防止

typescriptimport { useEffect } from 'react';
import { motion, useAnimation } from 'motion/react';

const SafeAnimation = () => {
  const controls = useAnimation();

  useEffect(() => {
    const animation = controls.start({ rotate: 360 });

    // クリーンアップでアニメーションを停止
    return () => {
      animation.then((anim) => anim.stop());
    };
  }, [controls]);

  return (
    <motion.div animate={controls}>
      安全なアニメーション
    </motion.div>
  );
};

アニメーション制御オブジェクトを適切にクリーンアップすることで、メモリリークを防げます。

まとめ

Motion は React でのアニメーション実装を革新的に簡単にしてくれる素晴らしいライブラリです。従来の CSS アニメーションでは困難だった複雑な制御も、宣言的で直感的な記述で実現できます。

この記事でご紹介した内容をおさらいしましょう:

  • Motion の概要: Framer Motion から進化した React 専用アニメーションライブラリ
  • 課題解決: CSS の限界や React での状態管理の複雑さを解消
  • 基本実装: フェード、スケール、位置移動の基本パターン
  • 最適化: レスポンシブ対応とパフォーマンス考慮

Motion を使うことで、ユーザー体験を大幅に向上させる美しいアニメーションを、少ないコードで実現できます。まずは簡単なフェードアニメーションから始めて、徐々に高度な機能に挑戦してみてください。

モダンな Web アプリケーション開発において、Motion は必須のツールと言えるでしょう。ぜひ皆様のプロジェクトにも導入して、ユーザーに愛されるインターフェースを作ってみてください。

関連リンク