【JavaScript】ページ上部へ戻るボタンの実装方法|スムーススクロール・フェードアニメーション・デザイン・アクセシビリティまで解説

長いページを読み進めた後にワンクリックでページの先頭に戻れる「ページ上部へ戻る」ボタンは、ユーザー体験を向上させる定番 UI です。この記事では、HTML・CSS・JavaScript だけでライブラリを使わずに実装する方法を、フェードアニメーション・デザイン・パフォーマンス最適化・アクセシビリティまで含めて解説します。

この記事でわかること
・HTML・CSS・JavaScript による基本実装
・スクロール量に応じたフェードイン・フェードアウトの表示切り替え
・window.scrollTo によるスムーススクロール
・矢印アイコンの CSS デザイン(SVG 不要)
・scroll イベントの throttle によるパフォーマンス最適化
・IntersectionObserver による軽量な表示制御
・prefers-reduced-motion・aria-label のアクセシビリティ対応
スポンサーリンク

基本実装(HTML・CSS・JavaScript)

HTML

HTML
<!-- ページ上部へ戻るボタン -->
<button id="back-to-top" class="back-to-top" aria-label="ページの先頭に戻る">
  <span class="back-to-top-arrow"></span>
</button>

aria-label を設定することで、スクリーンリーダーがボタンの役割を正しく読み上げます。アイコンだけのボタンには必ず付けましょう。

CSS

CSS
/* ボタン本体 */
.back-to-top {
  position: fixed;
  bottom: 24px;
  right: 24px;
  width: 48px;
  height: 48px;
  border: none;
  border-radius: 50%;
  background-color: #0284c7;
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  z-index: 1000;

  /* 初期状態: 非表示 */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.2s ease;
}

/* 表示状態 */
.back-to-top.is-visible {
  opacity: 1;
  visibility: visible;
}

/* ホバーエフェクト */
.back-to-top:hover {
  transform: translateY(-3px);
  background-color: #0369a1;
}

/* 矢印アイコン(CSS のみ) */
.back-to-top-arrow {
  display: block;
  width: 12px;
  height: 12px;
  border-top: 2px solid #fff;
  border-right: 2px solid #fff;
  transform: rotate(-45deg) translateY(2px);
}
矢印アイコンは border-top + border-right を 45° 回転させるだけで描画できます。SVG やアイコンフォントが不要で、色やサイズの変更も CSS だけで完結します。

JavaScript

JavaScript
const backToTop = document.getElementById("back-to-top");

// スクロール量に応じてボタンの表示・非表示を切り替え
window.addEventListener("scroll", () => {
  if (window.scrollY > 300) {
    backToTop.classList.add("is-visible");
  } else {
    backToTop.classList.remove("is-visible");
  }
});

// クリックでページ上部にスムーススクロール
backToTop.addEventListener("click", () => {
  window.scrollTo({
    top: 0,
    behavior: "smooth"
  });
});

これだけで基本的な「ページ上部へ戻る」ボタンが完成します。300px 以上スクロールするとフェードインし、クリックすると滑らかにページの先頭まで戻ります。

display: none / block の切り替えではなく opacity + visibility を使うことで、CSS の transition によるフェードアニメーションが実現できます。

display: none ではなく opacity + visibility を使う理由

方法 アニメーション クリック判定 描画コスト
display: none / block 不可(瞬時に切り替わる) 非表示時は不可 低い
opacity: 0 + visibility: hidden フェードイン・アウト可能 非表示時は不可 やや高い

visibility: hidden は要素がレイアウト上に残りますが、クリックイベントは発生しません。opacity: 0 だけだとクリックが透過してしまうため、必ず visibility と併用してください。

scroll イベントの throttle でパフォーマンスを最適化する

scroll イベントはスクロール中に毎秒数十回〜数百回発火します。ボタンの表示切り替えだけならそこまで重くありませんが、他のスクロール処理と組み合わせる場合は throttle(一定間隔に間引く)を導入すると安全です。

JavaScript(throttle 版)
function throttle(fn, interval) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

const backToTop = document.getElementById("back-to-top");

// 100ms ごとに1回だけ実行
window.addEventListener("scroll", throttle(() => {
  if (window.scrollY > 300) {
    backToTop.classList.add("is-visible");
  } else {
    backToTop.classList.remove("is-visible");
  }
}, 100));

IntersectionObserver で表示を制御する(scroll イベント不要)

scroll イベントの代わりに IntersectionObserver を使うと、スクロールイベントのリスナー登録自体が不要になります。ページ上部のセンチネル要素が画面外に出たらボタンを表示する仕組みです。

HTML
<!-- ページ上部にセンチネル(目印)を配置 -->
<div id="top-sentinel"></div>

<!-- ...ページのコンテンツ... -->

<button id="back-to-top" class="back-to-top" aria-label="ページの先頭に戻る">
  <span class="back-to-top-arrow"></span>
</button>
JavaScript
const backToTop = document.getElementById("back-to-top");
const sentinel = document.getElementById("top-sentinel");

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // センチネルが見えている = ページ上部にいる → ボタン非表示
      backToTop.classList.remove("is-visible");
    } else {
      // センチネルが見えていない = スクロール済み → ボタン表示
      backToTop.classList.add("is-visible");
    }
  });
});

observer.observe(sentinel);
方法 メリット デメリット
scroll イベント 閾値(300px 等)を柔軟に設定 イベント発火回数が多い
IntersectionObserver ブラウザが最適化、パフォーマンス良好 閾値の細かい調整がやや手間
パフォーマンスを重視するなら IntersectionObserver がベストです。スクロール量による細かい閾値制御が必要な場合は scroll イベント + throttle を使いましょう。

デザインバリエーション

角丸の四角形

CSS
.back-to-top {
  /* 丸形から角丸四角形に変更 */
  border-radius: 8px;
  width: 44px;
  height: 44px;
}

テキスト付きボタン

HTML
<button id="back-to-top" class="back-to-top back-to-top-text" aria-label="ページの先頭に戻る">
  <span class="back-to-top-arrow"></span>
  <span>TOP</span>
</button>
CSS
.back-to-top-text {
  flex-direction: column;
  gap: 2px;
  width: 56px;
  height: 56px;
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.05em;
}

フッター付近で位置を変える

フッターと重ならないように、フッター付近でボタンの位置を上げる実装です。

JavaScript
window.addEventListener("scroll", throttle(() => {
  const footer = document.querySelector("footer");
  const footerTop = footer.getBoundingClientRect().top;
  const windowHeight = window.innerHeight;

  if (footerTop < windowHeight) {
    // フッターが見えている → ボタンをフッターの上に移動
    const offset = windowHeight - footerTop + 24;
    backToTop.style.bottom = offset + "px";
  } else {
    backToTop.style.bottom = "24px";
  }
}, 100));

アクセシビリティと prefers-reduced-motion

アクセシビリティのチェックリスト

項目 対応方法
ボタンの役割 aria-label="ページの先頭に戻る"
キーボード操作 button 要素を使えば Enter / Space で自動的に動作
フォーカスリング ブラウザデフォルトの outline を消さない(消す場合は代替スタイルを設定)
モーション軽減 prefers-reduced-motion で即時スクロールに切り替え

prefers-reduced-motion への対応

OS で「視差効果を減らす」を有効にしているユーザーに配慮し、スムーススクロールを無効化します。

JavaScript
backToTop.addEventListener("click", () => {
  const prefersReduced = window.matchMedia(
    "(prefers-reduced-motion: reduce)"
  ).matches;

  window.scrollTo({
    top: 0,
    behavior: prefersReduced ? "auto" : "smooth"
  });
});
CSS
@media (prefers-reduced-motion: reduce) {
  .back-to-top {
    transition: none;
  }
}

完成コードまとめ

ここまでの内容を統合した、実務で使えるコードの全体像です。

完成版 JavaScript
document.addEventListener("DOMContentLoaded", () => {
  const backToTop = document.getElementById("back-to-top");

  // throttle ユーティリティ
  function throttle(fn, interval) {
    let lastTime = 0;
    return (...args) => {
      const now = Date.now();
      if (now - lastTime >= interval) {
        lastTime = now;
        fn(...args);
      }
    };
  }

  // スクロール量に応じた表示切り替え
  window.addEventListener("scroll", throttle(() => {
    if (window.scrollY > 300) {
      backToTop.classList.add("is-visible");
    } else {
      backToTop.classList.remove("is-visible");
    }
  }, 100));

  // クリックでページ上部にスクロール
  backToTop.addEventListener("click", () => {
    const prefersReduced = window.matchMedia(
      "(prefers-reduced-motion: reduce)"
    ).matches;
    window.scrollTo({
      top: 0,
      behavior: prefersReduced ? "auto" : "smooth"
    });
  });
});

関連記事

よくある質問

Qボタンが 300px スクロール後にパッと表示されて不自然です。フェードさせるには?
ACSS で opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; を設定し、JavaScript では display ではなく classList.add("is-visible")opacity: 1; visibility: visible; に切り替えてください。display: none → block の切り替えでは transition が効きません。
Qscroll イベントのパフォーマンスが心配です。
A単純な表示切り替えだけなら体感上の問題はありません。他のスクロール処理と併用する場合は throttle(100ms 程度)を導入するか、IntersectionObserver に切り替えると scroll イベント自体が不要になります。
QWordPress で使うにはどうすればよいですか?
A子テーマの footer.php に HTML を、style.css に CSS を、functions.phpwp_enqueue_script を使って JS ファイルを読み込むのが一般的です。テーマやプラグインに戻るボタン機能が内蔵されている場合は、重複しないよう確認してください。
Qスマートフォンでボタンがコンテンツに被って邪魔です。
Aボタンのサイズを小さくする(40px 程度)、bottom / right の余白を調整する、またはスマートフォンでは非表示にする(@media (max-width: 480px) { .back-to-top { display: none; } })といった対応が考えられます。
Qフッターとボタンが重なってしまいます。
AJavaScript で footer.getBoundingClientRect().top を監視し、フッターが画面内に入ったらボタンの bottom 値を動的に増やしてフッターの上に逃がす実装が効果的です。本記事のデザインバリエーションの項で具体的なコードを紹介しています。

まとめ

「ページ上部へ戻る」ボタンは、HTML・CSS・JavaScript の基本だけで実装できるシンプルな UI です。実装のポイントは以下の通りです。

  • フェードアニメーション: opacity + visibility + transition で滑らかに
  • スムーススクロール: window.scrollTo({ top: 0, behavior: "smooth" })
  • パフォーマンス: throttle または IntersectionObserver でスクロールイベントを最適化
  • アクセシビリティ: aria-label・prefers-reduced-motion への対応

CSS の矢印アイコンやフッター回避のテクニックを組み合わせれば、デザインにこだわった実用的なボタンが完成します。