ページ内のアンカーリンク(<a href="#contact">など)をクリックした際、画面をスルスルと滑らかに移動させるためにscroll-behavior: smooth;を追加したものの、「なぜか一瞬でパッと切り替わってしまう」「CSSを書いたのに全く動かない」と悩んだことはないでしょうか。
ライブラリや複雑なJavaScriptを使わなくても、CSSを1行指定するだけでスムーススクロールは実現できます。
しかし、指定する要素の場所(htmlかbodyか)、実際にスクロールしているコンテナ要素の特定、あるいは対象となる操作の誤解があると、意図した通りに機能しません。
この記事では、scroll-behavior: smoothが効かない主な原因と解決策、JavaScriptとの関係、そして位置ズレ時の対処法までを分かりやすく解説します。
scroll-behavior: smoothが効かないときにまず確認すること
コードを複雑にする前に、まずは以下のチェックリストを確認してください。
bodyではなくhtml(または:root)に指定しているか- ページ全体ではなく、特定の
divやモーダル内部がスクロールしていないか - マウスホイールやトラックパッドの操作自体を滑らかにしようとしていないか
- JavaScriptの
event.preventDefault()でリンク動作を止めていないか prefers-reduced-motionに応じてscroll-behavior: autoへ切り替えるCSSを入れていないか
scroll-behavior: smoothは何を滑らかにする?
トラブルの原因を見る前に、このプロパティが「何を対象にしているのか」を正しく把握しておきましょう。
<!-- リンク先 -->
<a href="#section-2">2章へ移動</a>
<!-- 移動先の見出し -->
<h2 id="section-2">第2章のコンテンツ</h2>
/* ページ全体の基本指定 */
html {
scroll-behavior: smooth;
}
たったこれだけで、ページ内のアンカーリンクをクリックした際のスクロールが滑らかになります。
マウスホイールなどの通常操作は対象外
ここで初心者が最も誤解しやすいのが、「マウスホイールの回転や、スマホで指を滑らせたときのスクロールまで滑らかになる」と思ってしまう点です。
CSSOM View仕様において、scroll-behaviorが滑らかにする対象は、ページ内ナビゲーション(アンカーリンク)や、スクロール系API(window.scrollTo()など)によって発生するスクロールです。
ユーザー自身が手動で行う通常のホイール操作やスワイプ操作には影響しません。
「効いていない」と感じたときは、まず「アンカーリンクをクリックしたときの動き」を確認してみてください。
原因1:bodyにscroll-behaviorを書いている
CSSを書いたのにページ全体のスクロールが滑らかにならない場合、よくある原因の1つがbody要素に指定してしまっているケースです。
/* ✕ ページ全体のviewportスクロールには伝播しない */
body {
scroll-behavior: smooth;
}
/* ◯ ページ全体ならルート要素へ */
html {
scroll-behavior: smooth;
}
背景色(background-color)など一部のCSSプロパティはbodyに指定してもページ全体(ビューポート)へ自動的に適用されますが、CSS仕様上、scroll-behaviorはbodyに書いてもビューポートへ伝播しません。
ルート要素(htmlまたは:root)に指定して初めて、ブラウザの画面全体(ビューポート)に対するスムーススクロールとして機能します。
原因2:実際にスクロールしている要素が別にある
「htmlに書いたのに、一部のエリアだけ一瞬で飛んでしまう」という場合は、スクロールしている本体がページ全体ではなく、特定のコンテナ要素(divなど)であるケースが考えられます。
例えば、以下のように高さを固定してoverflow-y: auto;を設定したサイドバーやモーダル、チャット欄などです。
<div class="modal-body">
<a href="#modal-bottom">一番下へ</a>
<!-- 長いコンテンツ -->
<div id="modal-bottom">終端</div>
</div>
/* ✕ htmlに指定しても、このコンテナ内のスクロールには効かない */
html {
scroll-behavior: smooth;
}
/* ◯ スクロール機構を持っている要素自身に指定する */
.modal-body {
height: 400px;
overflow-y: auto;
scroll-behavior: smooth; /* ここに指定する */
}
スクロールに関するCSSは、「実際にスクロールバーを持って動いている要素(スクロールコンテナ)」に直接指定する必要があります。
原因3:JavaScript側でアンカーリンクの動作を止めている
過去の制作コードやWordPressプラグイン、別スクリプトが干渉しているパターンです。
// ✕ クリックイベントで標準の挙動を遮断している
const links = document.querySelectorAll('a[href^="#"]');
links.forEach(link => {
link.addEventListener('click', (event) => {
event.preventDefault(); // ここでアンカー遷移そのものが止まる
});
});
event.preventDefault()を呼んでしまうと、ブラウザ標準のフラグメントナビゲーションが完全に無効化されるため、CSSのscroll-behavior: smoothも実行されません。
また、JavaScript側でbehavior: 'instant'を指定したスクロール処理や、独自のアニメーション処理が実行されている場合は、CSS側で期待した動きにならないことがあります。
JavaScriptでスムーススクロールを制御する方法
アンカーリンクではなく、ボタンクリック時やフォーム送信後にJavaScriptから滑らかに移動させたい場合は、スクロール系メソッドのbehaviorオプションを使用します。
1. window.scrollTo()を使う(座標指定)
特定のスクロール位置(ピクセル数)へ移動させたい場合に使います。
window.scrollTo({
top: 500,
behavior: 'smooth' // 'smooth', 'instant', 'auto'
});
2. element.scrollIntoView()を使う(要素指定)
特定の要素が見える位置まで画面をスクロールさせたい場合に適しています。
const target = document.querySelector('#contact');
target.scrollIntoView({
behavior: 'smooth'
});
※behavior: 'auto'を指定した場合は、実際にスクロールするスクロールコンテナのscroll-behaviorの計算値に従って動作します(CSSでsmoothが指定されていれば滑らかに動きます)。
スムーズには動くがスクロール位置がずれる場合
「滑らかに動くようにはなったけれど、ヘッダーに隠れて見出しが読めない」という現象が起きた場合は、CSSのscroll-margin-topで調整します。
/* 固定ヘッダーの高さが 80px ある場合 */
.section-title {
scroll-margin-top: 80px;
}
scroll-margin-topを指定しておくと、アンカー遷移やscrollIntoView()の際に、その要素の上部に指定した余白分を空けてスクロールが停止します。
詳しい仕組みや微調整の手順については、以下の個別記事で詳しく解説しています。
scroll-behaviorではスクロール速度や時間を指定できない
「もう少しゆっくりスクロールさせたい」「2秒かけて滑らかに動かしたい」と考えることがありますが、scroll-behavior: smoothにはアニメーション速度や時間を調整するパラメータは存在しません。
どのようなイージング(加減速)で、何ミリ秒かけて移動するかはブラウザ(User Agent)の実装に委ねられています。
通常のサイトナビゲーションであれば、ブラウザ標準に任せることで実装をシンプルにでき、追加ライブラリも不要です。
prefers-reduced-motionへの配慮
視覚的なアニメーションによってめまいや不快感を覚えるユーザーのために、OS側で「視覚効果を減らす」設定が用意されています。
アクセシビリティを考慮し、ユーザーが動きの軽減を求めている場合はスムースアニメーションを停止させるCSSを用意しておくのが現代のコーディングでは推奨されます。
html {
scroll-behavior: smooth;
}
/* ユーザーが「視覚効果を減らす」を設定している場合は即時移動に戻す */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
それでもsmoothにならないときの確認手順
これまでのポイントを踏まえ、トラブル時の切り分け順を整理します。
- 指定先は
htmlか:bodyに書いていないか確認(bodyの値はビューポートへ伝播しない) - スクロールコンテナの特定:ページ全体ではなく
overflow: auto / scrollを持った要素の中で動いていないか - リンク先要素の確認:
<a href="#target">のid="target"がHTML上に正しく存在するか - JavaScriptの干渉確認:
clickイベントでpreventDefault()していないか、behavior: 'instant'や独自アニメーションで上書きされていないか確認 - 操作の切り分け:マウスホイール操作ではなく、アンカーリンクをクリックして動作を確認しているか
- メディアクエリの確認:Reduce Motion時に
scroll-behavior: autoへ変更するCSSが適用されていないか - ブラウザ側の挙動確認:
scroll-behaviorはUser Agentが無視することも仕様上許可されているため、別ブラウザでも同じ挙動になるか確認する
CSS・scrollTo・scrollIntoViewの使い分け
スクロールを実装する主要な手段の特徴と用途の整理です。
| 手法 | 実装手段 | 主な用途・向いている場面 |
|---|---|---|
scroll-behavior: smooth |
CSS | ページ全体のアンカーリンク移動をシンプルに一括設定したいとき |
window.scrollTo() |
JavaScript | 「トップへ戻る」ボタンなど、特定のスクロール座標へ移動させたいとき |
element.scrollIntoView() |
JavaScript | フォームのエラー箇所など、特定の要素の位置へスクロールさせたいとき |
scroll-margin-top |
CSS | 固定ヘッダーに隠れないよう、停止位置に余白を持たせたいとき |
まとめ
scroll-behavior: smoothは、わずか1行で手軽にスムーススクロールを実現できる優れたCSSプロパティです。
- 指定先のルール:
bodyではなくhtml(ルート要素)、または実際にスクロールしているコンテナ要素に直接指定する - 対象の範囲:アンカーリンクやプログラムによるスクロールが対象であり、通常のマウスホイール操作は滑らかにならない
- JavaScriptとの連携:クリックイベントを無効化していないか確認し、JSから制御する場合は
behavior: 'smooth'を指定する - 停止位置のズレ:固定ヘッダーで見出しが隠れる場合は、移動先要素に
scroll-margin-topを指定する
「動かないからとりあえずJavaScriptプラグインを導入する」とする前に、まずはhtmlへの指定と実際のスクロール要素を確認してみてください。