まずはページトップボタンの動きを確認する

前回の「モーダルウィンドウの記事」では、dialog要素を使った開閉処理や、背景クリック、Escapeキー、フォーカス管理について確認しました。

今回は、ページを下へスクロールしたときに表示される「ページトップボタン」を作ります。

ページトップボタンは小さなUIですが、スクロール量の取得、イベント処理、表示状態の切り替え、特定の位置へのスクロールなど、実務でよく使う処理がまとまっています。

ページトップボタンは、次のように動作します。

  1. ページを下へスクロールする
  2. 一定のスクロール量を超えると、画面右下にボタンが表示される
  3. ボタンはスクロールしても同じ位置に固定される
  4. ボタンをクリックするとページ上部へ戻る
  5. ページ上部へ戻ると、ボタンが再び非表示になる

実際の動きを確認してみましょう

このページを下へスクロールすると、画面右下にページトップボタンが表示されます。クリックして、ページ上部へ戻る動きを確認してください。

今回作る機能を確認する

今回の基本コードでは、次の機能を実装します。

  • ページトップボタンを画面右下へ固定する
  • ページ上部ではボタンを非表示にする
  • 300px以上スクロールしたらボタンを表示する
  • クリックしたらページ上部へ戻る
  • 通常は滑らかにスクロールする
  • 動きを減らす設定では、アニメーションせずに移動する

scrollイベントは文書がスクロールしたときに発生し、window.scrollYから現在の縦方向のスクロール量を取得できます。

ページトップボタンを表示する条件を整理する

初心者がスクロール系の実装をする際、「スクロール中の表示制御」と「クリック後の移動処理」を一度に考えて混乱してしまうことがあります。 機能ごとに分けて考えましょう。

表示を制御する処理

  • scrollイベントでスクロールされたことを検知する
  • scrollYで現在位置(どれくらい下へ移動したか)を確認する
  • 条件に応じてhidden属性を切り替えてボタンを表示・非表示にする

クリックして移動する処理

  • ボタンがクリックされる
  • scrollTotop: 0(ページ上端)を渡す
  • ページ上部へ移動する

この2つの処理を別々に作っていくのがポイントです。

この記事で学べること

  • position: fixedを使った画面への固定
  • scrollイベントとscrollYプロパティの使い方
  • hidden属性による表示・非表示の切り替え
  • window.scrollToを使ったスムーススクロール
  • prefers-reduced-motion(動きを減らす設定)への配慮

ページトップボタンのHTMLを書く

まずはボタンのHTMLです。 初期状態(一番上にいるとき)は見えなくしておきたいので、あらかじめhidden属性を付けておき、必要なスクロール量に達したときだけJavaScriptで外す設計にします。

<button
  class="page-top-button js-page-top-button"
  type="button"
  aria-label="ページ上部へ戻る"
  hidden
>
  <span
    class="page-top-button__arrow"
    aria-hidden="true"
  >
    ↑
  </span>
</button>

CSSで画面右下に固定する

ボタンを常に画面の右下に表示するため、position: fixedを使います。 また、hidden属性がついたときに確実に非表示になるようdisplay: none;の指定も明示しておきます。

.page-top-button {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 100;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #2563eb;
  color: #fff;
  font-size: 1.5rem;
  cursor: pointer;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.2);
}

.page-top-button:hover {
  background: #1d4ed8;
}

.page-top-button:focus-visible {
  outline: 3px solid rgba(37, 99, 235, 0.45);
  outline-offset: 3px;
}

/* hidden属性がついているときは確実に非表示にする */
.page-top-button[hidden] {
  display: none;
}

/* スマートフォン向けの調整 */
@media (max-width: 520px) {
  .page-top-button {
    right: 16px;
    bottom: 16px;
    width: 52px;
    height: 52px;
  }
}

JavaScriptの基本形

「表示の切り替え」と「クリックによる移動」の2つの処理を、JavaScriptで記述します。 今回は全体を(() => { ... })();という即時関数で囲み、他のコードと変数が衝突しないように書いています。

(() => {
  const pageTopButton = document.querySelector('.js-page-top-button');

  if (!pageTopButton) {
    return;
  }

  // ボタンを表示するスクロール量(px)
  const showButtonPosition = 300;

  // OSの設定で「動きを減らす」が有効か確認
  const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');

  // 1. スクロール量に応じて表示・非表示を切り替える処理
  const updateButtonVisibility = () => {
    // 現在のスクロール量が300px未満なら hidden = true(隠す)
    pageTopButton.hidden = window.scrollY < showButtonPosition;
  };

  window.addEventListener('scroll', updateButtonVisibility);

  // 2. クリックしたらページ上部へ戻る処理
  pageTopButton.addEventListener('click', () => {
    window.scrollTo({
      top: 0,
      behavior: reduceMotion.matches ? 'instant' : 'smooth',
    });
  });

  // 読み込み直後に一度実行し、初期状態を正しくする
  updateButtonVisibility();
})();

コードの意味を1行ずつ見る

scrollイベントを登録する

window.addEventListener('scroll', updateButtonVisibility);

ユーザーが画面をスクロールするたびに、関数(表示を切り替える処理)を呼び出します。

scrollイベントの注意点

scrollイベントは、スクロール中に短い間隔で繰り返し発生します。今回のようにスクロール量を確認してボタン1つの表示状態を切り替える程度であれば、基本コードのままで問題ありません。 複雑な計算や多数の要素の更新を行う場合は、処理回数を減らす方法やIntersectionObserverの利用も検討します。

scrollYで縦方向の位置を確認する

window.scrollY

現在の画面が一番上から何ピクセル下へスクロールされたかを取得できます。これが300px以上かどうかで、ボタンの表示・非表示を切り替えます。

hiddenで表示状態を変更する

pageTopButton.hidden = window.scrollY < showButtonPosition;

条件式の判定結果(trueまたはfalse)を、そのままhiddenプロパティに代入しています。スクロール量が300未満ならtrueとなりボタンが隠れます。

scrollToでページ上部へ戻る

window.scrollTo({ top: 0, ... })

指定した座標へスクロールさせるメソッドです。top: 0を指定することでページの上端へ移動します。

matchMediaで動きの設定を確認する

behavior: reduceMotion.matches ? 'instant' : 'smooth'

prefers-reduced-motionを使い、OS等で動きを減らす設定が有効になっているかを確認します。設定がオンならinstant(即座に移動)、オフならsmooth(滑らかにスクロール)を適用しています。

最後にupdateButtonVisibility()を実行する理由

コードの最後でupdateButtonVisibility();を単独で呼び出しています。 これは、ページを途中までスクロールした状態で再読み込み(リロード)した場合や、ブラウザが以前のスクロール位置を復元した場合にも、最初から正しい表示状態にするためです。

完成コード

HTML、CSS、JavaScriptを合わせた完成コードです。

コピーして動作確認しやすいよう、スクロールするためのダミーコンテンツ(.demo-content)も用意しています。

<link rel="stylesheet" href="./style.css">
<script src="./script.js" defer></script>

<main class="demo-content">
  <h1>ページトップボタンのサンプル</h1>

  <p>
    ページを下へスクロールしてください。
    300px以上スクロールすると、
    画面右下にボタンが表示されます。
  </p>

  <p class="demo-content__bottom">
    ページ下部のサンプルコンテンツです。
  </p>
</main>

<button
  class="page-top-button js-page-top-button"
  type="button"
  aria-label="ページ上部へ戻る"
  hidden
>
  <span
    class="page-top-button__arrow"
    aria-hidden="true"
  >
    ↑
  </span>
</button>
/* デモ用の高さ確保 */
.demo-content {
  min-height: 200vh;
  padding: 40px 20px;
}

.demo-content__bottom {
  margin-top: 120vh;
}

/* ページトップボタン */
.page-top-button {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 100;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #2563eb;
  color: #fff;
  font-size: 1.5rem;
  cursor: pointer;
  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.2);
}

.page-top-button:hover {
  background: #1d4ed8;
}

.page-top-button:focus-visible {
  outline: 3px solid rgba(37, 99, 235, 0.45);
  outline-offset: 3px;
}

.page-top-button[hidden] {
  display: none;
}

@media (max-width: 520px) {
  .page-top-button {
    right: 16px;
    bottom: 16px;
    width: 52px;
    height: 52px;
  }
}
(() => {
  const pageTopButton = document.querySelector('.js-page-top-button');

  if (!pageTopButton) {
    return;
  }

  const showButtonPosition = 300;

  const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');

  const updateButtonVisibility = () => {
    pageTopButton.hidden = window.scrollY < showButtonPosition;
  };

  window.addEventListener('scroll', updateButtonVisibility);

  pageTopButton.addEventListener('click', () => {
    window.scrollTo({
      top: 0,
      behavior: reduceMotion.matches ? 'instant' : 'smooth',
    });
  });

  updateButtonVisibility();
})();

実際に触ってみよう

完成コードは、下のCodePenでも確認できます。Result領域を下へスクロールし、300px以上でボタンが表示されること、クリックするとページ上部へ戻ることを確認してください。

See the Pen Web制作JS|ページトップボタン|基本サンプル by WebToolGen (@webtoolgen) on CodePen.

カスタマイズして使ってみよう

表示を開始する位置を変更する

const showButtonPosition = 300;の数値を変更することで、どのくらいスクロールしたらボタンを出すかを調整できます。メインビジュアル(ヒーロー画像)の高さを取得して、それを過ぎたら表示する、といった動的な指定も可能です。

ボタンの位置や大きさを変更する

CSSの.page-top-buttonrightbottomwidthheightの数値を変更します。スマートフォンの場合は親指で押しやすいサイズ(44px〜)を確保しましょう。

矢印アイコンを変更する

HTMLの<span>↑</span>部分を、SVGアイコンやFont Awesome、アイコン画像などに差し替えるだけで変更できます。

フッターや固定バナーとの重なりを避ける

スマートフォンで画面下部に固定の追従バナーがある場合や、フッターまでスクロールした際にボタンを止めたい場合は、CSSのbottom値を調整するか、JavaScriptで要素の位置を計算して付与するクラスを切り替えるなどの発展的な処理が必要になります。

よくある失敗と解決法

最初からボタンが表示されている

HTMLにhidden属性を書き忘れているか、CSSで.page-top-button[hidden] { display: none; }の指定が抜けている可能性があります。

スクロールしてもボタンが表示されない

JavaScriptのコンソールにエラーが出ていませんか?また、js-page-top-buttonというクラス名がHTMLとJavaScriptで一致しているか確認しましょう。

クリックしてもページ上部へ戻らない

window.scrollToの綴りや、top: 0、波括弧{}の記述が正しいか確認します。また、クリックイベントが登録されているか、JavaScriptのコンソールにエラーが出ていないかも確認してください。 原因を切り分けるときは、一度behaviorinstantに変更し、ページ上部へ移動する処理自体が動いているかを確認すると分かりやすくなります。

ボタンがほかの要素の後ろに隠れる

ヘッダーやモーダルなど、ほかの要素のz-indexのほうが大きくなっているケースです。.page-top-buttonz-indexを調整して前面に出しましょう。

スマートフォンで固定バナーと重なる

画面下部に固定メニューや広告バナーがある場合は、その高さ分だけCSSのbottomの数値を大きくして、ボタンの位置を引き上げてください。

アクセシビリティで確認したいこと

JavaScriptで移動させる場合はbuttonを使う

ページ上部にIDを付け、<a href="#top">のようなページ内リンクで戻る方法もあります。

今回は、クリック時にwindow.scrollTo()を実行し、スクロール方法もJavaScriptで制御するため、button要素を使用します。 button要素であれば、Tabキーによるフォーカスや、Enterキー・Spaceキーによる操作も標準で利用できます。

ボタンの目的をaria-labelで伝える

ボタンの中身が「↑」などの記号やアイコンだけの場合、スクリーンリーダーでは意味が通じません。<button aria-label="ページ上部へ戻る">と指定し、何のボタンなのかを明示しましょう。

キーボードフォーカスを見えるようにする

CSSで:focus-visibleを設定し、Tabキーによる操作時にボタンが選択されていることが視覚的に分かるようにします。

非表示中は操作対象から外す

今回hidden属性を使っている理由はこれです。CSSのopacity: 0;などで見えなくしているだけだと、目に見えないボタンにTabキーでフォーカスが当たってしまい混乱を招きます。hiddenを使えば、見えない時は操作対象からも外れます。

スムーズスクロールを強制しない

人によっては、画面が大きく動くスムーズスクロールによって画面酔いを起こすことがあります。今回のように利用者の設定に応じて動きを減らすことは、スムーズスクロールを実装する際の重要な配慮です。

この記事のまとめ

今回は、スクロールに応じて表示され、クリックでページ上部に戻る「ページトップボタン」を作成しました。

  • scrollイベントとscrollYで現在の状態を監視する
  • 条件に応じてhidden属性で表示・非表示を切り替える
  • scrollToを使って特定の座標へ移動する
  • OSのアニメーション設定に配慮する

このように「状態の検知」「見た目の変更」「ユーザーのアクションに対する処理」を組み合わせていくことが、JavaScriptを使ったUI実装の基本です。 一つひとつの処理はシンプルなので、ぜひ実際のサイトにも組み込んでみてください。