長いページを読み進めた後にワンクリックでページの先頭に戻れる「ページ上部へ戻る」ボタンは、ユーザー体験を向上させる定番 UI です。この記事では、HTML・CSS・JavaScript だけでライブラリを使わずに実装する方法を、フェードアニメーション・デザイン・パフォーマンス最適化・アクセシビリティまで含めて解説します。
・HTML・CSS・JavaScript による基本実装
・スクロール量に応じたフェードイン・フェードアウトの表示切り替え
・window.scrollTo によるスムーススクロール
・矢印アイコンの CSS デザイン(SVG 不要)
・scroll イベントの throttle によるパフォーマンス最適化
・IntersectionObserver による軽量な表示制御
・prefers-reduced-motion・aria-label のアクセシビリティ対応
基本実装(HTML・CSS・JavaScript)
HTML
<!-- ページ上部へ戻るボタン --> <button id="back-to-top" class="back-to-top" aria-label="ページの先頭に戻る"> <span class="back-to-top-arrow"></span> </button>
aria-label を設定することで、スクリーンリーダーがボタンの役割を正しく読み上げます。アイコンだけのボタンには必ず付けましょう。
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
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(一定間隔に間引く)を導入すると安全です。
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 を使うと、スクロールイベントのリスナー登録自体が不要になります。ページ上部のセンチネル要素が画面外に出たらボタンを表示する仕組みです。
<!-- ページ上部にセンチネル(目印)を配置 --> <div id="top-sentinel"></div> <!-- ...ページのコンテンツ... --> <button id="back-to-top" class="back-to-top" aria-label="ページの先頭に戻る"> <span class="back-to-top-arrow"></span> </button>
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 | ブラウザが最適化、パフォーマンス良好 | 閾値の細かい調整がやや手間 |
デザインバリエーション
角丸の四角形
.back-to-top {
/* 丸形から角丸四角形に変更 */
border-radius: 8px;
width: 44px;
height: 44px;
}
テキスト付きボタン
<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>
.back-to-top-text {
flex-direction: column;
gap: 2px;
width: 56px;
height: 56px;
font-size: 10px;
font-weight: bold;
letter-spacing: 0.05em;
}
フッター付近で位置を変える
フッターと重ならないように、フッター付近でボタンの位置を上げる実装です。
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 で「視差効果を減らす」を有効にしているユーザーに配慮し、スムーススクロールを無効化します。
backToTop.addEventListener("click", () => {
const prefersReduced = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
window.scrollTo({
top: 0,
behavior: prefersReduced ? "auto" : "smooth"
});
});
@media (prefers-reduced-motion: reduce) {
.back-to-top {
transition: none;
}
}
完成コードまとめ
ここまでの内容を統合した、実務で使えるコードの全体像です。
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"
});
});
});
関連記事
- スムーススクロールを実装する方法 — scrollIntoView・scrollTo・CSS scroll-behavior の使い分け
- 上向きスクロールで表示されるヘッダーの作り方
- ハンバーガーメニューの実装方法 — CSS アニメーション・オーバーレイ・アクセシビリティ
- アコーディオンの作り方 — CSS アニメーション・アクセシビリティ対応
- classList の使い方完全ガイド — add・remove・toggle でクラスを操作
よくある質問
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 が効きません。IntersectionObserver に切り替えると scroll イベント自体が不要になります。footer.php に HTML を、style.css に CSS を、functions.php で wp_enqueue_script を使って JS ファイルを読み込むのが一般的です。テーマやプラグインに戻るボタン機能が内蔵されている場合は、重複しないよう確認してください。bottom / right の余白を調整する、またはスマートフォンでは非表示にする(@media (max-width: 480px) { .back-to-top { display: none; } })といった対応が考えられます。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 の矢印アイコンやフッター回避のテクニックを組み合わせれば、デザインにこだわった実用的なボタンが完成します。