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
でコンポーネントをアンマウントしたら、スクロールリスナーをクリア(削除)にするために呼び出し関数を返します。
カスタムフックの下部に最後はreturn
でscrollposition
を返します。
そしてこの、作成したカスタムフックを任意のファイルにインポートして新しい変数として割り当て、使用していくだけとなります。
// App.js import useScroll from "./useSroll"; const App = () => { const scrollPosition = useScroll(); console.log(scrollPosition); return(<div> </div>) }
これはコンポーネントに対してアニメーションを追加する場合に役立つことがあります。
ページ上のDOM要素にアクセス
ページ上のDOM要素にアクセスする為には、当ブログの記事で一度解説したのですがuseRefフックを使用する必要があります。
useRefフックに精通がない場合はまず、下記を参照ください。
※ 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
という名前のカスタムフックを定義し、 useRef
、 useEffect
、および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の扱い方にも気を付けて下さい。
本日は以上となります。
最後まで読んで頂きありがとうございます
この記事が気に入ったら、ブックマークし他の方に共有してください。