📱

React/Next.js環境での効率的なウィンドウリサイズ検知

に公開

ウェブサイトやアプリケーションでスマートフォン幅では特定のアニメーションを非表示にするなど、画面サイズに応じて振る舞いを変更したい場合があります。ReactやNext.jsではhooksを活用することで、VanillaJSとは異なるアプローチでこうした制御を効率的に行うことができます。

VanillaJSとReact/Next.jsでの制御方法

両者のコードを比較

VanillaJs

const animationController = {
  // 初期化関数
  init() {
	this.updateAnimation();
    window.addEventListener("resize", this.updateAnimation);
  },
  
  // アニメーション更新関数
  updateAnimation() {
	const windowWidth = window.innerWidth;
    
    if (windowWidth < 768) {
      // モバイル向けのアニメーション
    } else {
      // デスクトップ向けのアニメーション
    }
  },
  
  // 明示的なクリーンアップ関数
  destroy() {
    window.removeEventListener("resize", this.updateAnimation);
  }
};

document.addEventListener("DOMContentLoaded", () => {
  animationController.init();
  
  // ⚠️重要: ページ遷移や要素削除時にdestroy()を呼び出す必要がある
  // 何らかのタイミングで: animationController.destroy();
});

React/Next.js

const [isMobile, setIsMobile] = useState<boolean>(false);

useEffect(() => {

  const handleResize = () => {
    setIsMobile(window.innerWidth < 768);
  };

  handleResize();

  window.addEventListener('resize', handleResize);

  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []);

return ( 
  <div> {isMobile ? 
    ( /** モバイル幅での処理 */ ) : ( /** PC幅での処理 */ );

両者を比較すると、Reactのフックを活用したアプローチには以下のメリットがあります:

  1. コードの見通しがよい - 関連する処理が近くにまとまっており、処理の流れが把握しやすい
  2. 自動クリーンアップ - コンポーネントのアンマウント時に自動的にクリーンアップされるため、リソースリークを防ぎやすい
  3. ステート管理との連携 - ブラウザ幅の変化をisMobileというステートで管理することで、JSXでの条件分岐が簡単になる

GSAP活用した例(React/Next.js)

const sampleRef = useRef<HTMLDivElement>(null);

useGSAP(() => {
  // ScrollTriggerを制御
  ScrollTrigger.getAll().forEach(st => {
    if (st.vars.trigger === sampleRef.current) {
    st.kill();
    }
  });

  if (!isMobile && sampleRef.current) {
    // PCでのアニメーション
     gsap.fromTo(
       sampleRef.current, {
       ...
       },
      {
        ...
        scrollTrigger: {
        ...
       },
      }
     );
    }
  // スマホでのアニメーションまたは状態の初期化
  else if (isMobile && sampleRef.current) {
    gsap.to(
      sampleRef.current, {
        ...
      }
    );
  }
}, [isMobile]); // isMobileの変更を監視

return (
  <>
    <div ref={sampleRef}></div>
  </>
)

少し補足ですが、

  • ScrollTrigger.getAll().forEach(...)は、sampleRef.currentをトリガーとして使用している既存のすべてのScrollTriggerインスタンスをクリーンアップするために書かれています。
  • その後、条件に応じて(isMobileの値に基づいて)新しいScrollTriggerを作成しています。

クリーンアップ関数の重要性

クリーンアップ関数を実装しないと、以下の問題が発生する可能性があります:

  • メモリリーク - イベントリスナーが蓄積され、メモリ使用量が増加
  • パフォーマンス低下 - 不要になったコンポーネントのリスナーも実行され続ける
  • 予期しない動作 - 古いコンポーネントのロジックが新しいUIに影響を与える

注意点:Next.jsでupdateAnimation関数をそのまま使用する場合の問題

Next.js(React)環境でupdateAnimation関数をそのまま使用すると、以下の問題が発生します:

  • SSRエラー - サーバーサイドレンダリング時にwindowdocumentなどのブラウザAPIが存在しないため、window is not definedなどのエラーが発生
  • メモリリーク - コンポーネントのライフサイクルと連携していないため、コンポーネントがアンマウントされてもイベントリスナーが残り続ける
  • 予期しない再レンダリング - Reactの再レンダリングのたびにリスナーが重複して登録される可能性がある

こうした理由から、React環境では必ずuseEffectなどのフックを活用してブラウザAPIにアクセスし、適切なタイミングでクリーンアップを行うことが推奨されています。

まとめ:React/Next.js環境での効率的なウィンドウリサイズ対応のベストプラクティス

  1. React/Next.jsではフックを活用する
    • VanillaJSのようなグローバルな関数ではなく、コンポーネント内のuseEffectを使用する
    • コンポーネントのライフサイクルに合わせてリソースを管理する
  2. stateでブラウザ幅の状態を管理する
    • isMobileのようなステートを使って、画面幅の変化を管理する
    • これによりJSXでの条件分岐が簡単になる
  3. 自動クリーンアップの利点を活かす
    • useEffectの戻り値でクリーンアップ関数を実装し、自動的なリソース解放を行う
    • VanillaJSのように明示的なdestroy関数の呼び出し忘れによるメモリリークを防止する
  4. SSRの問題に注意する
    • Next.jsのSSR環境では、ブラウザAPI(window, document)へのアクセスをuseEffect内に限定する

React/Next.jsでのウィンドウリサイズ検知は、フックの活用により簡潔かつ安全に実装できます。特にuseEffectとuseStateの組み合わせにより、コンポーネントのライフサイクルと連携した効率的なコードが書けるようになり、メモリリークなどの問題を自動的に防止できることが大きなメリットです。

Discussion