ページ内のアンカーリンク(<a href="#contact">など)をクリックした際、画面をスルスルと滑らかに移動させるためにscroll-behavior: smooth;を追加したものの、「なぜか一瞬でパッと切り替わってしまう」「CSSを書いたのに全く動かない」と悩んだことはないでしょうか。

ライブラリや複雑なJavaScriptを使わなくても、CSSを1行指定するだけでスムーススクロールは実現できます。

しかし、指定する要素の場所(htmlかbodyか)、実際にスクロールしているコンテナ要素の特定、あるいは対象となる操作の誤解があると、意図した通りに機能しません。

この記事では、scroll-behavior: smoothが効かない主な原因と解決策、JavaScriptとの関係、そして位置ズレ時の対処法までを分かりやすく解説します。

scroll-behavior: smoothが効かないときにまず確認すること

コードを複雑にする前に、まずは以下のチェックリストを確認してください。

  1. bodyではなくhtml(または:root)に指定しているか
  2. ページ全体ではなく、特定のdivやモーダル内部がスクロールしていないか
  3. マウスホイールやトラックパッドの操作自体を滑らかにしようとしていないか
  4. JavaScriptのevent.preventDefault()でリンク動作を止めていないか
  5. 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にならないときの確認手順

これまでのポイントを踏まえ、トラブル時の切り分け順を整理します。

  1. 指定先はhtmlか:bodyに書いていないか確認(bodyの値はビューポートへ伝播しない)
  2. スクロールコンテナの特定:ページ全体ではなくoverflow: auto / scrollを持った要素の中で動いていないか
  3. リンク先要素の確認:<a href="#target">のid="target"がHTML上に正しく存在するか
  4. JavaScriptの干渉確認:clickイベントでpreventDefault()していないか、behavior: 'instant'や独自アニメーションで上書きされていないか確認
  5. 操作の切り分け:マウスホイール操作ではなく、アンカーリンクをクリックして動作を確認しているか
  6. メディアクエリの確認:Reduce Motion時にscroll-behavior: autoへ変更するCSSが適用されていないか
  7. ブラウザ側の挙動確認: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への指定と実際のスクロール要素を確認してみてください。