Next.js と TypeScript で、スクロール後「上に戻る」ボタンを実装

TwitterFacebookHatena

TL;DR

このページでは、スクロール時のイベント実行について詳しく解説しますね。一言でいうとスクロール時のイベント実行とは、ユーザーがページをスクロールするときに特定の処理を実行することです。例えば、ページの一部を見えるようにする、あるいはページの上に戻るボタンを表示するなどの処理を考えてみてください。

開発環境 バージョン
Next.js 13.4.4
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

スクロール時のイベント実行

スクロール時のイベント実行とは、ユーザーがウェブページをスクロールするときに特定の処理を実行する考え方のことを指します。スクロールに反応して何らかのアクションを実行することで、ユーザー体験を向上させたり、ユーザーに情報をより効率的に提供したりすることができます。

具体的な例としては、次のようなシナリオを考えてみてください。

  • ユーザーがページを下にスクロールするとナビゲーションバーが消え、上にスクロールすると再び表示される。
  • ユーザーがページを下にスクロールすると「ページトップに戻る」ボタンが表示される。

これらの挙動は、ウェブサイトのインターフェースをユーザーが期待するように動作させるために重要です。それでは、基本的なスクロールイベントリスナーを設定する方法を見てみましょう。

// components/ScrollListener.tsx

// useEffect を React からインポート
import { useEffect } from 'react'

// ScrollListener という関数コンポーネントを作成
const ScrollListener = () => {
  // useEffect フックを使用。
  // このフックは、コンポーネントのマウント後、アンマウント前、
  // および更新後に実行される処理を記述するためのもの
  useEffect(() => {
    // handleScroll 関数を定義。
    // この関数は window(ブラウザウィンドウ)のスクロール位置を取得し、
    // それをコンソールに出力する
    const handleScroll = () => {
      console.log(window.scrollY)
      // window.scrollY は、現在のスクロール位置をピクセル単位で表す
    }

    // window(ブラウザウィンドウ)のスクロールイベントに対して、
    // handleScroll 関数をイベントリスナーとして登録
    window.addEventListener('scroll', handleScroll)

    // useEffect の cleanup 処理。
    // コンポーネントがアンマウントされる(ページから削除される)ときに実行される
    // ここでは、window のスクロールイベントリスナーを削除している。
    // これにより、コンポーネントが存在しない場合でも
    // handleScroll 関数が実行されないようにする
    return () => {
      window.removeEventListener('scroll', handleScroll)
    }
  }, []) // useEffect の依存配列。
  // ここが空の配列([])なので、
  // この useEffect フック内の処理は
  // コンポーネントの初回マウント時とアンマウント時のみに実行される

  // ScrollListener コンポーネントの表示部分。
  // ここではシンプルな div 要素を表示している
  return <div>Scroll Listener</div>
}

// ScrollListener コンポーネントを
// export(他のファイルからインポート可能にする)
export default ScrollListener

このコードは、スクロールイベントを検知してコンソールに現在のスクロール位置を出力します。useEffect フック内でスクロールイベントリスナーを設定して、スクロール時に handleScroll 関数が実行されるようにしています。そして、コンポーネントのアンマウント時にイベントリスナーを削除することで、メモリリークを防ぎます。

Next.js で、スクロール時のイベントを実装

では、具体的なシナリオを見ていきましょう。ここでは、ページをスクロールすると「ページトップに戻る」ボタンが表示され、そのボタンをクリックするとページのトップに戻るという機能を実装します。

まずは、そのボタンのコンポーネントを作成します。

// components/ScrollToTopButton.tsx

// useState, useEffect を React からインポート
import { useState, useEffect } from 'react'

// Props の型定義。showAfter はスクロール量
type Props = {
  showAfter: number // スクロール量後にボタン表示
}

// ScrollToTopButton 関数コンポーネントの定義
const ScrollToTopButton = ({ showAfter }: Props) => {
  // isShown という state を定義。初期状態は false(非表示)
  const [isShown, setIsShown] = useState(false)

  // useEffect フックを使用
  useEffect(() => {
    // handleScroll 関数を定義。スクロール量をチェック
    const handleScroll = () => {
      // スクロール量が showAfter 以上なら表示
      if (window.scrollY >= showAfter) {
        setIsShown(true)
      } else {
        setIsShown(false) // それ以外は非表示
      }
    }

    // スクロールイベントに対し handleScroll を登録
    window.addEventListener('scroll', handleScroll)

    // cleanup 処理。コンポーネントアンマウント時に実行
    return () => {
      // スクロールイベントリスナーを削除
      window.removeEventListener('scroll', handleScroll)
    }
  }, [showAfter]) // showAfter の変更時に再実行

  // handleClick 関数を定義。ページトップへスムーズにスクロール
  const handleClick = () => {
    window.scrollTo({ top: 0, behavior: 'smooth' })
  }

  // isShown が false の場合、null を返し何も表示しない
  if (!isShown) return null

  // isShown が true の場合、ボタンを表示
  return <button onClick={handleClick}>Scroll to top</button>
}

// ScrollToTopButton コンポーネントをエクスポート
export default ScrollToTopButton

Emotion でスタイル付けを行う

次に、Emotion を使ってスタイルを付けてみましょう。Emotion は JavaScript 内に CSS を書くことができる CSS-in-JS ライブラリで、スタイルの再利用やコンポーネントベースのスタイリングを容易にします。

// components/ScrollToTopButton.tsx

// css を @emotion/react からインポート
import { css } from '@emotion/react'
// useState, useEffect を React からインポート
import { useState, useEffect } from 'react'

// Props の型定義。showAfter はスクロール量
type Props = {
  showAfter: number // スクロール量後にボタン表示
}

// ScrollToTopButton 関数コンポーネントの定義
const ScrollToTopButton = ({ showAfter }: Props) => {
  // isShown という state を定義。初期状態は false(非表示)
  const [isShown, setIsShown] = useState(false)

  // useEffect フックを使用
  useEffect(() => {
    // handleScroll 関数を定義。スクロール量をチェック
    const handleScroll = () => {
      // スクロール量が showAfter 以上なら表示
      if (window.scrollY >= showAfter) {
        setIsShown(true)
      } else {
        setIsShown(false) // それ以外は非表示
      }
    }

    // スクロールイベントに対し handleScroll を登録
    window.addEventListener('scroll', handleScroll)

    // cleanup 処理。コンポーネントアンマウント時に実行
    return () => {
      // スクロールイベントリスナーを削除
      window.removeEventListener('scroll', handleScroll)
    }
  }, [showAfter]) // showAfter の変更時に再実行

  // handleClick 関数を定義。ページトップへスムーズにスクロール
  const handleClick = () => {
    window.scrollTo({ top: 0, behavior: 'smooth' })
  }

  // isShown が false の場合、null を返し何も表示しない
  if (!isShown) return null

  // isShown が true の場合、ボタンを表示
  // css props により buttonStyle を適用
  return (
    <button onClick={handleClick} css={buttonStyle}>
      Scroll to top
    </button>
  )
}

// buttonStyle の定義。css prop によるスタイル付け
const buttonStyle = css`
  position: fixed;
  bottom: 20px;
  right: 20px;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  background: #333;
  color: #fff;
  cursor: pointer;
`

// ScrollToTopButton コンポーネントをエクスポート
export default ScrollToTopButton

ここでは、ボタンコンポーネントに CSS を適用しています。Emotion は css 関数とテンプレートリテラルを使用して CSS を定義し、その定義をコンポーネントに適用します。この方法で、ボタンが右下の位置に固定され、適切なスタイリングが施されます。

呼び出し側のコード

最後に、このボタンコンポーネントをページから呼び出してみましょう。

// pages/index.tsx

import ScrollToTopButton from '../components/ScrollToTopButton'

const HomePage = () => {
  return (
    <div>
      {/* Other components... */}
      <ScrollToTopButton showAfter={300} />
    </div>
  )
}

export default HomePage

このように、ScrollToTopButton コンポーネントは showAfter プロパティを受け取り、その値に基づいて表示・非表示を切り替えます。

以上が Next.js と TypeScript を使ったスクロールイベントの実装です。このような実装を通して、ユーザーがページの特定の部分にスクロールしたときに特定の動作を行うといったインタラクティブな体験を提供できます。さらに、イベントリスナーの追加と削除を適切に行うことで、パフォーマンスの問題を防ぐこともできます。

Next.js と TypeScript で、スクロール後「上に戻る」ボタンを実装