JavaScriptで特定の要素までスクロールさせたいとき、手軽に使えるのが element.scrollIntoView() です。

しかし、実際に使ってみると「固定ヘッダーの後ろに見出しが潜り込んで隠れてしまう」「意図した位置より上や下にずれてしまう」といった問題によく遭遇します。

この記事では、scrollIntoView() で位置がずれる原因から、CSSの scroll-margin-top と組み合わせた最短の調整方法、block オプションの違い、そして window.scrollTo() との使い分けまでを解説します。

scrollIntoViewでスクロール位置がずれる原因

まずは scrollIntoView() がどのように要素を画面内へ移動させているのかを確認します。

scrollIntoViewは対象要素を表示領域へ移動するメソッド

scrollIntoView() は、呼び出した要素がブラウザの表示領域(スクロールポート)の中に入るようにスクロールさせるメソッドです。

const target = document.querySelector('#target');

// 対象要素が見える位置までスクロールする
target.scrollIntoView();

デフォルトではblock: “start”として扱われる

CSSOM View仕様において、引数を省略した際のオプションは block: "start"、inline: "nearest" が既定値(デフォルト)として扱われます。

つまり、「対象要素の上端を表示領域の上端に合わせる」 ようにスクロールします。

【ブラウザの表示領域(ビューポート)】
┌────────────────────────────────────────┐
│ #target の上端                         │ ← scrollIntoView()が合わせる位置
│ ┌────────────────────────────────────┐ │
│ │ 固定ヘッダー(height: 80px)       │ │ ← 上から重なって隠す
│ └────────────────────────────────────┘ │
│ #target の本文...                      │
│                                        │
└────────────────────────────────────────┘

scrollIntoView() は画面上にある position: fixed; や position: sticky; の要素を自動で避けてくれるわけではありません。

そのため、固定ヘッダーが存在するサイトでは、ヘッダーの高さ分だけ要素の上部が隠れてしまいます。

固定ヘッダーに隠れる場合はscroll-margin-topで調整する

固定ヘッダーと重なる問題を解決する最短の方法は、スクロール先の要素に対して CSS の scroll-margin-top を指定すること です。

/* 固定ヘッダーの高さが 80px の場合 */
#target {
  scroll-margin-top: 80px;
}
const target = document.querySelector('#target');

target.scrollIntoView({
  behavior: 'smooth',
  block: 'start'
});

MDNの仕様解説でも、固定ヘッダーやツールバーがある場合のオフセット調整には scroll-margin-top(下方向の場合は scroll-margin-bottom)を使用することが示されています。

JavaScript側で引き算の計算を行わなくても、ブラウザが scroll-margin-top の値を考慮してスクロールの停止位置を調整してくれます。

アンカーリンクでも同じように固定ヘッダーへ見出しが隠れる場合は、scroll-margin-topの基本的な使い方を解説したこちらの記事も参考にしてください。

ヘッダー高さ+少し余白を取る

実務では、ヘッダーの真下に要素が密着するよりも、少し余裕(16px〜24px程度)を持たせた方が読みやすくなります。

#target {
  /* ヘッダー高さ 80px + 余白 16px */
  scroll-margin-top: 96px;
}

scrollIntoViewのblockで停止位置を変更できる

scrollIntoView() の block オプションを指定すると、要素を表示領域の「どの縦位置(垂直方向)」に配置するかを変更できます。

target.scrollIntoView({
  block: 'center' // 画面中央付近へスクロール
});
block の値スクロール後の配置位置主な用途・向いているケース
start(既定値)対象要素の上端を表示領域の上端に合わせる見出しやセクションの先頭へのスクロール
center対象要素を表示領域の中央付近に合わせるフォームのエラー箇所、注目させたい画像やカード
end対象要素の下端を表示領域の下端に合わせるチャットの最新メッセージ、フッター付近のUI
nearest移動量が最小になるよう、最も近い端に合わせるドロップダウン内の選択肢、リスト項目の表示

behavior: “smooth”は位置調整ではなくスクロール方法の指定

scrollIntoView({ behavior: 'smooth' }) はよく使われるオプションですが、これは スクロールのアニメーション方法を指定するもの であり、停止位置をずらす指定ではありません。

target.scrollIntoView({
  behavior: 'smooth', // スクロールの仕方(スムーズに移動)
  block: 'start'       // 配置位置(上端に合わせる)
});
  • behavior:どう移動するか(smooth: なめらかに移動 / instant: 即時移動 / auto: CSSの scroll-behavior に従う)
  • block:画面内のどこへ配置するか(start / center / end / nearest)
  • scroll-margin-top:対象要素のスクロール位置に上側の余白を持たせる

scroll-margin-topとblockは役割が違う

それぞれの役割を整理すると以下のようになります。

指定項目主な役割
blockスクロール領域内での基本的な配置位置を決める
scroll-margin-top対象要素のスクロール位置に上側の余白を持たせる
behaviorスクロール方法を指定する

固定ヘッダーがある一般的な横書きページでは、block: "start" と scroll-margin-top を組み合わせると調整しやすくなります。

scrollIntoViewが思った位置にならないときの確認点

scrollIntoView() を実行しても期待した位置で止まらない場合は、以下の点を確認してみてください。

1. blockの指定を確認する

意図せず block: "center" や block: "nearest" が残っていないか確認します。

見出しの先頭へ綺麗にスクロールさせたい場合は block: "start" を明示するのが基本です。

2. scroll-margin-topが対象要素に付いているか

JavaScriptで指定した target 要素そのものに scroll-margin-top が効いているか確認してください。

親要素や子要素の見出しだけに CSS を指定していると、target 自身のスクロール位置には反映されません。

3. 別のスクロールコンテナがないか

scrollIntoView() は、対象要素を表示するために祖先要素のスクロールコンテナ(overflow: auto や overflow: scroll が指定された要素)も順にスクロール させます。

モーダル内や高さ固定のリスト(.list { height: 400px; overflow-y: auto; })など、入れ子のスクロール領域がある場合、ページ全体とコンテナ内部の両方が動いて位置がずれることがあります。

4. sticky・fixedヘッダーの高さが途中で変わっていないか

スクロール開始後にヘッダーが小さくなるアニメーション(縮小ヘッダー)や、途中でバナーが出現・非表示になるレイアウトの場合、固定値の scroll-margin-top と実際のヘッダー高さにズレが生じることがあります。

5. CSSのscroll-paddingが指定されていないか

スクロールコンテナ(html など)に scroll-padding-top が指定されており、さらに対象要素にも scroll-margin-top を指定しているなど、scroll-padding-top と scroll-margin-top の両方が効いていると、想定より大きく間隔が空くことがあります。

JavaScriptで座標を計算してoffsetを引く方法は必要?

過去のWeb制作では、固定ヘッダーの高さを引くために window.scrollTo() と座標計算を組み合わせる手法が多用されていました。

// 従来の座標計算によるスクロール
const headerHeight = 80;
const elementPosition = target.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.scrollY - headerHeight;

window.scrollTo({
  top: offsetPosition,
  behavior: 'smooth'
});

現在の結論

固定ヘッダー分を空けるだけなら、まず scroll-margin-top を検討する のが実務的です。

CSSで scroll-margin-top を設定しておけば、JavaScript側はシンプルな target.scrollIntoView({ behavior: 'smooth', block: 'start' }) だけで完結します。

scrollIntoViewとwindow.scrollToはどう使い分ける?

メソッド向いている用途特徴
element.scrollIntoView()特定の要素までスクロールさせたいとき対象を指定するだけで簡潔。CSSの scroll-margin と連携しやすい
window.scrollTo()具体的な数値・座標へスクロールさせたいとき座標を細かく制御できるが、要素位置の計算コードが必要になることがある

「トップへ戻る」ボタンのように特定の座標(0, 0)へ移動したい場合は window.scrollTo()、特定の要素を表示させたい場合は scrollIntoView() を選ぶと、コードをシンプルに保ちやすくなります。

まとめ

element.scrollIntoView() によるスクロール位置の調整は、メソッド自体のオプションと CSS プロパティの役割を分けて考えると整理しやすくなります。

  • 基本挙動:既定値は block: "start"(要素の上端が表示領域の上端に合わさる)
  • 固定ヘッダー対策:対象要素に CSS の scroll-margin-top を指定する
  • 画面内の配置指定:中央に寄せたいなら block: "center" など block オプションで切り替える
  • 移動方法の指定:behavior: "smooth" はスクロールのアニメーション指定(位置調整ではない)
  • メソッドの選択:要素への移動は scrollIntoView()、数値・座標指定なら window.scrollTo()

単純な固定ヘッダーのオフセットのためだけに複雑な JavaScript 座標計算を書く前に、まずは scroll-margin-top との組み合わせで対応できないか確認してみてください。