スクロール処理に関するカスタムフック

  • ReactJS
  • 小ネタ

フロントエンド開発をしていると、ウィンドウのスクロールに関する処理を書くことがあるかと思います。

例えば、「Y軸方向に一定量スクロールしたときにボタンを表示する」や「上方向にスクロールされたときにヘッダーを表示する」など色々な場面が考えられます。

「スクロールイベントを登録して、スクロールされたときにWindowオブジェクトからウィンドウのスクロール量を取得する」といった流れで実装できます。

今回はY軸方向のスクロール量を取得するカスタムフックとそれを用いてスクロール方向を取得するカスタムフックを作ってみようと思います。

実装

useScrollPosition()

以下はY軸方向のスクロール量を取得するカスタムフックのコードです。

/**
  * Y軸方向のスクロール量を取得するカスタムフック
  */
const useScrollPosition = () => {
  const isProcessing = useRef(false)
  const [positionY, setPositionY] = useState(0)

  useEffect(() => {
    const handler = () => {
      if (isProcessing.current) return
      isProcessing.current = true
      // Window.requestAnimationFrame()でpositionYステートの更新を間引く
      window.requestAnimationFrame(() => {
        isProcessing.current = false
        setPositionY(window.scrollY)
      })
    }
    // スクロールイベントの登録
    document.addEventListener('scroll', handler, { passive: true })
    return () => {
      // スクロールイベントの解除
      document.removeEventListener('scroll', handler)
    }
  }, [])

  // スクロール量を返却する
  return positionY
}

特筆する点は、Window.requestAnimationFrame()positionYステートの更新を間引いているところです。

スクロールイベントを仕込む際に、イベントの抑制はよくやられます。setTimeout()で間引くこともできますが、処理する環境に合わせて適切なタイミングで処理してくれるWindow.requestAnimationFrame()の方がベターだと思います。

詳しくは Document: scroll event - Web API | MDN をご覧ください。

useScrollDirection()

以下はスクロール方向を取得するカスタムフックのコードです。

/**
  * スクロール方向の識別子
  */
const ScrollDirection = {
  Up: 'up',
  Down: 'down',
} as const
export type ScrollDirectionType = typeof ScrollDirection[keyof typeof ScrollDirection]

/**
  * スクロール方向を取得するカスタムフック
  */
const useScrollDirection = () => {
  const positionY = useScrollPosition()
  // 直前までのスクロール量
  const previousPositionY = useRef(positionY)
  const [direction, setDirection] = useState<ScrollDirectionType | null>(null)

  useEffect(() => {
    // 直前までのスクロール量と現在のスクロール量を比較することで
    // 軸方向について上と下どちらにスクロールしているかを判定する
    if (positionY < previousPositionY.current) {
      setDirection(ScrollDirection.Up)
    } else if (positionY > previousPositionY.current) {
      setDirection(ScrollDirection.Down)
    } else {
      setDirection(null)
    }

    previousPositionY.current = positionY
  }, [positionY])

  return direction
}

先ほど作成したuseScrollPosition()を用いて、軸方向について上と下どちらにスクロールしているかを判定し、方向の情報を返却するという内容です。

おわりに

以上、簡単ですがカスタムフックを実装してみました。

今回はY軸方向についてのスクロールを取り扱いましたが、X軸方向についても拡張する形で実装できるかと思います。

余談ですが、便利なカスタムフックをまとめたライブラリも世の中にはあるので、それを利用するのも全然ありです。

この記事を共有

アバター

K.Utsunomiya
主にWeb技術について投稿していきます。
詳しいプロフィール