React カスタムフック スクロールイベント

Reactのカスタムフック、スクロールイベント

スクロールイベント

スクロールイベントは、Webページで最もよく使用されるイベントの1つです。

ほとんどの場合では必要なのは『一番上までスクロール』することだけです。

長いコンテンツページがありそこに移動すると、下にスクロールされたままになり、一番上に戻るのが非常に面倒になるためです。

ですが、その前にReactのコンポーネントを作成しスクロールイベントリスナーで現在のウィンドウ位置を取得する方法を学ばなければいけません。

本日はその取得方法を解説していきます。

Reactフックでカスタムフックを作成し、再利用可能なコンポーネントにします。

それでは簡単な例で学んでいきましょう。

現在のウィンドウ位置を取得

// useScroll.js

import { useEffect, useState } from "react";

const useScroll = () => {

  const [scrollPosition, setScrollPosition] = useState(0);

  useEffect(() => {

    const PositionUp = () => {
      setScrollPosition(window.pageYOffset);
    }

    window.addEventListener("scroll", PositionUp);
    PositionUp();

    return () => window.removeEventListener("scroll", PositionUp);
  }, []);

  return scrollPosition;
};

export default useScroll;

こちらのcode penのコンソールで出力を確認できます。

それでは、コードを分割しながら説明していきます。

const useScroll = () => {

  const [scrollPosition, setScrollPosition] = useState(0);

上記の通り、useScrollカスタムフックとなります。

まずはscrollpositionのローカル状態でuseStateで初期値をゼロとして定義します。

  useEffect(() => {

    const PositionUp = () => {
      setScrollPosition(window.pageYOffset);
    }

    window.addEventListener("scroll", PositionUp);
    PositionUp();

useEffectフック内にaddEventListenerでスクロールイベントを追加し、ユーザーがスクロールするたびにscrollpositionの初期値である状態を更新します。

状態の変更はpageYOffsetを使用します。

これは、Windowのプロパティで垂直方向のスクロール量となります。

Windowはブラウザタブになります、なのでタブのスクロールを監視します。

    return () => window.removeEventListener("scroll", PositionUp);
  }, []);

  return scrollPosition;
};

export default useScroll;

removeEventListenerコンポーネントをアンマウントしたら、スクロールリスナーをクリア(削除)にするために呼び出し関数を返します。

カスタムフックの下部に最後はreturnscrollpositionを返します。

そしてこの、作成したカスタムフックを任意のファイルにインポートして新しい変数として割り当て、使用していくだけとなります。

// App.js

import useScroll from "./useSroll";

const App = () => {

const scrollPosition = useScroll();

  console.log(scrollPosition);


return(<div>

</div>)

}

これはコンポーネントに対してアニメーションを追加する場合に役立つことがあります。

ページ上のDOM要素にアクセス

ページ上のDOM要素にアクセスする為には、当ブログの記事で一度解説したのですがuseRefフックを使用する必要があります。

useRefフックに精通がない場合はまず、下記を参照ください。

dev-k.hatenablog.com

※ useCallbackフックも使用します。

一般的で非常に簡単な例で確認していきましょう。

スクロールイベントリスナーをページのdivにアクセスします。

// App.js

import { useRef, useEffect, useCallback } from "react";

const App = () => {

  const ref = useRef();

  const handleScrolling = useCallback(() => {
//再レンダリング間で関数を保持する
    console.log("scrolling");

  }, []);

  useEffect(() => {

    const div = ref.current;

    div.addEventListener('scroll', handleScrolling);
//値が変更されたときにのみトリガーされる
  }, [handleScrolling]);

  return (
<div className='App-header'>
    <div className="scrollContainer" ref={ref}>
      <div className="content">
コンソールで確認しながら下にスクロールして下さい。
</div>
      </div>
    </div>
  );
}

既存のApp.cssにスタイルを追加します。

/* App.css  */

.scrollContainer {
  background-color: rgba(0, 0, 0, 0.8);
  color: #fff;
  border: 3px solid black;
  width: 400px;
  height: 100px;
  overflow: scroll;

}

.content {
  height: 800px;
}

DEMO

実際の出力はこちらのcode penのコンソールで確認できます。

// App.js

import { useRef, useEffect } from "react";

const App = () => {

  const ref = useRef();

  const handleScrolling = useCallback(() => {
    console.log("scrolling");

  }, []);

handleScrollingはスクロールでのイベントリスナーとなります。

useCallbackフックで、handleScrolling関数そのものをメモ化します。

コンポーネントが再レンダリングされるときには、キャッシュから関数を取得するだけで、今度は同じ関数になり、子コンポーネントでの再レンダリングはトリガーされません。

しかし、useCallbackフックは、再レンダリング時のフェーズのみ有効であることです。

初回レンダリング時には役に立たないだけでなく、有害でもあります。

useCallbackおよびuseMemoフックが役立つのと同様に、このフックがこれらの値や関数をキャッシュに格納するためにメモリの一部を使用していることも、あなたは理解しておいて下さい。

ですので、初回レンダリング時に、アプリの動作がわずかですが遅くなる可能性がございます。

乱用しすぎの最適化注意ということです。

useCallbackフックについては、こちらのReact useMemoおよびuseCallbackフックの使用方法で解説しておりますので参照ください。

// App.js

  useEffect(() => {

    const div = ref.current;

    div.addEventListener('scroll', handleScrolling);

  }, [handleScrolling]);

useEffectフックである2番目の引数(配列)の依存関係を確認してみると

handleScrollingという関数が変更された時のみにフックが実行されます。

この依存関係はフック内で使用していますので自動で追加されていき、つまりこのフックは一度だけ実行されます。

上記のコードをカスタムフックとして独立させる場合、下記のようにします。

import { useRef, useEffect, useCallback } from "react";

const useScrolling = () => {

  const ref = useRef();

  const handleScrolling = useCallback(() => {
    console.log("scrolling");
  }, []);

  useEffect(() => {

    const div = ref.current;

    div.addEventListener('scroll', handleScrolling);

    return () => {
      div.removeEventListener('scroll', handleScrolling);
    };
  }, [handleScrolling]);

  return ref;
};

export default useScrolling;

useScrolling という名前のカスタムフックを定義し、 useRefuseEffect 、およびuseCallbackフックを使用して、スクロール操作を処理するためのコードをカプセル化します。

そして、 useEffectフックでイベントリスナーを追加し、スクロール時に呼び出すコールバック関数を登録します。

また、イベントリスナーの登録が不要になった時に、クリーンアップ関数を返すことで、メモリリークを防止します。

そして、このカスタムフックを他のコンポーネントで使用するには、下記のように呼び出します。

import useScrolling from "./useScrolling";

const App = () => {

  const ref = useScrolling();

  return (
    <div className='App-header'>
      <div className="scrollContainer" ref={ref}>
        <div className="content">
          コンソールで確認しながら下にスクロールして下さい。
        </div>
      </div>
    </div>
  );
}

上記のように、useScrollingカスタムフックをAppコンポーネントで呼び出すことができます。

このコンポーネントrefを返し、それを scrollContainer要素に割り当てることで、スクロールイベントを処理できるようになります。

最後に

Reactでは、現在のスクロール位置を追跡することは非常に困難でもあります、アプリケーションに不要な負荷をかけ、ユーザーエクスペリエンスを劇的に低下させる可能性があることに注意しながら扱うようにして下さい。

ページ内のDOM要素にアクセスする場合にはRefの扱い方にも気を付けて下さい。

本日は以上となります。

最後まで読んで頂きありがとうございます

この記事が気に入ったら、ブックマークし他の方に共有してください。

プライバシーポリシー

© 2023 Icons8 LLC. All rights reserved.

© [deve.K], 2023. React logo is a trademark of Facebook, Inc. JavaScript is a trademark of Oracle Corporation and/or its affiliates. jQuery and the jQuery logo are trademarks of the JS Foundation. TypeScript and the TypeScript logo are trademarks of the Microsoft Corporation. Next.js and the Next.js logo are trademarks of Vercel, Inc. Firebase and the Firebase logo are trademarks of Google LLC. All logos edited by [deve.K].