T-CREATOR

失敗しない!React のアニメーションでパフォーマンスを落とさないコツ

失敗しない!React のアニメーションでパフォーマンスを落とさないコツ

React でアニメーションを実装する際、多くの開発者が直面するのがパフォーマンスの問題です。美しいアニメーションを作りたい気持ちは理解できますが、ユーザー体験を損なってしまっては本末転倒ですよね。

この記事では、React アニメーションでパフォーマンスを落とさないための実践的なテクニックを紹介します。初心者から上級者まで、明日から使える具体的な解決策をお届けします。

パフォーマンス問題の原因を理解する

React アニメーションでパフォーマンスが低下する主な原因は、不要な再レンダリングレイアウトスラッシングです。

よくある失敗パターン

javascript// ❌ 悪い例:毎回新しいオブジェクトを作成
const [animation, setAnimation] = useState({
  opacity: 0,
  transform: 'translateY(20px)',
});

useEffect(() => {
  // 毎回新しいオブジェクトが作成され、再レンダリングが発生
  setAnimation({
    opacity: 1,
    transform: 'translateY(0px)',
  });
}, []);

このコードの問題点は、setAnimation で新しいオブジェクトを作成することで、React が毎回再レンダリングを実行してしまうことです。

パフォーマンスに影響する要因

要因影響度説明
再レンダリング頻度不要な再レンダリングが 60fps を阻害
DOM 操作レイアウトスラッシングの原因
メモリ使用量アニメーションライブラリの重さ
CSS プロパティレイアウトを引き起こすプロパティ

CSS vs JavaScript アニメーションの使い分け

パフォーマンスを最優先に考えるなら、CSS アニメーションを基本とし、JavaScript は必要な場合のみ使用するのが鉄則です。

CSS アニメーションの利点

css/* ✅ 良い例:CSS トランジション */
.fade-in {
  opacity: 0;
  transform: translateY(20px);
  transition: all 0.3s ease-out;
}

.fade-in.visible {
  opacity: 1;
  transform: translateY(0);
}
javascript// CSS クラスを切り替えるだけ
const [isVisible, setIsVisible] = useState(false);

useEffect(() => {
  setIsVisible(true);
}, []);
// <div className={`fade-in${isVisible ? ' visible' : ''}`}>...</div>

JavaScript アニメーションが必要な場合

javascript// スクロール量に応じて値を更新する例
const [scrollY, setScrollY] = useState(0);

useEffect(() => {
  const handleScroll = () => {
    setScrollY(window.scrollY);
  };
  window.addEventListener('scroll', handleScroll);
  return () =>
    window.removeEventListener('scroll', handleScroll);
}, []);

React アニメーションライブラリの選び方

ライブラリ選びは、プロジェクトの要件とパフォーマンスを天秤にかけて決めることが重要です。

主要ライブラリの比較

ライブラリバンドルサイズパフォーマンス学習コスト
CSS Transitions0KB最高
React Spring13KB
Framer Motion35KB
React Transition Group5KB

軽量なアニメーション実装

javascript// カスタムフックで軽量アニメーション
import { useState, useEffect } from 'react';

const useFadeIn = (duration = 300) => {
  const [isVisible, setIsVisible] = useState(false);
  useEffect(() => {
    setIsVisible(true);
  }, []);
  return {
    opacity: isVisible ? 1 : 0,
    transition: `opacity ${duration}ms ease-out`,
  };
};
javascript// 使用例
const MyComponent = () => {
  const fadeInStyle = useFadeIn(500);
  return (
    <div style={fadeInStyle}>
      フェードインするコンテンツ
    </div>
  );
};

パフォーマンス最適化の基本テクニック

1. useMemo と useCallback の活用

javascript// ❌ 悪い例:毎回新しい関数が作成される
const handleAnimation = () => {
  // アニメーション処理
};

// ✅ 良い例:メモ化された関数
import { useCallback } from 'react';
const handleAnimation = useCallback(() => {
  // アニメーション処理
}, []);

2. アニメーション値の最適化

javascript// ❌ 悪い例:文字列連結による再計算
const transform = `translateX(${x}px) translateY(${y}px)`;

// ✅ 良い例:useMemo で最適化
import { useMemo } from 'react';
const transform = useMemo(
  () => `translateX(${x}px) translateY(${y}px)`,
  [x, y]
);

3. レイアウトスラッシングの回避

javascript// ❌ 悪い例:レイアウトを引き起こすプロパティ
const style = {
  width: element.offsetWidth + 10, // レイアウト発生
  height: element.offsetHeight + 10, // レイアウト発生
};

// ✅ 良い例:transform を使用
const style = {
  transform: 'scale(1.1)', // GPU アクセラレーション
  willChange: 'transform', // ブラウザに最適化を指示
};

実装例:スムーズなアニメーションの作り方

パフォーマンス重視のフェードイン

javascript// 最適化されたフェードインコンポーネント
import { useState, useEffect, useMemo } from 'react';

const OptimizedFadeIn = ({ children, delay = 0 }) => {
  const [isVisible, setIsVisible] = useState(false);
  useEffect(() => {
    const timer = setTimeout(
      () => setIsVisible(true),
      delay
    );
    return () => clearTimeout(timer);
  }, [delay]);
  const style = useMemo(
    () => ({
      opacity: isVisible ? 1 : 0,
      transform: isVisible
        ? 'translateY(0)'
        : 'translateY(20px)',
      transition:
        'opacity 0.3s ease-out, transform 0.3s ease-out',
      willChange: 'opacity, transform',
    }),
    [isVisible]
  );
  return <div style={style}>{children}</div>;
};

スクロールアニメーションの最適化

javascript// スロットリング付きスクロールアニメーション
import { useState, useEffect } from 'react';

const useScrollAnimation = () => {
  const [scrollY, setScrollY] = useState(0);
  useEffect(() => {
    let ticking = false;
    const handleScroll = () => {
      if (!ticking) {
        window.requestAnimationFrame(() => {
          setScrollY(window.scrollY);
          ticking = false;
        });
        ticking = true;
      }
    };
    window.addEventListener('scroll', handleScroll, {
      passive: true,
    });
    return () =>
      window.removeEventListener('scroll', handleScroll);
  }, []);
  return scrollY;
};

インタラクティブなホバーアニメーション

css/* CSS で実装する軽量ホバーアニメーション */
.hover-card {
  transform: translateY(0);
  transition: transform 0.2s ease-out;
  will-change: transform;
}

.hover-card:hover {
  transform: translateY(-4px);
}

デバッグと計測ツールの活用

React DevTools での最適化

javascript// プロファイリング用のコンポーネント
import React from 'react';
const ProfiledComponent = React.memo(({ children }) => {
  console.log('ProfiledComponent rendered');
  return <div>{children}</div>;
});

パフォーマンス計測

javascript// アニメーションパフォーマンスの計測
const measureAnimationPerformance = (callback) => {
  const start = performance.now();
  callback();
  requestAnimationFrame(() => {
    const end = performance.now();
    console.log(`Animation took ${end - start}ms`);
  });
};

よくあるエラーと解決策

javascript// ❌ エラー:Maximum update depth exceeded
const [count, setCount] = useState(0);

useEffect(() => {
  setCount(count + 1); // 無限ループ
}, [count]);

// ✅ 解決策:依存配列を適切に設定
const [count2, setCount2] = useState(0);

useEffect(() => {
  setCount2((prev) => prev + 1);
}, []); // 空の依存配列
javascript// ❌ エラー:Can't perform a React state update on an unmounted component
useEffect(() => {
  const timer = setTimeout(() => setState(newValue), 1000); // アンマウント後に実行される可能性
  return () => clearTimeout(timer);
}, []);

// ✅ 解決策:クリーンアップ関数でキャンセル
useEffect(() => {
  let isMounted = true;
  const timer = setTimeout(() => {
    if (isMounted) {
      setState(newValue);
    }
  }, 1000);
  return () => {
    isMounted = false;
    clearTimeout(timer);
  };
}, []);

まとめ

React アニメーションでパフォーマンスを落とさないコツは、適切なツールの選択最適化の実践にあります。

まずは CSS アニメーションから始め、必要に応じて JavaScript ライブラリを導入する。そして、useMemouseCallback を活用し、レイアウトスラッシングを避ける。

最も重要なのは、ユーザー体験を最優先に考えることです。美しいアニメーションも、パフォーマンスが悪ければ意味がありません。

明日から実践できる具体的なテクニックを紹介しましたが、これらは一朝一夕に身につくものではありません。継続的な学習と実践を通じて、パフォーマンスと美しさを両立するアニメーション開発者を目指してください。

関連リンク