1. ホーム
  2. CSS

【CSS】scroll-margin・scroll-padding の使い方|アンカーリンクでスクロールしたときの位置ズレ(固定ヘッダーに隠れる)を直す方法

Share

ページ内リンク(#id へのジャンプ)を押すと、目的の見出しへスクロールしてくれます。ところが画面上部に固定ヘッダーがあると、ジャンプ先の見出しがヘッダーの裏に隠れてしまい、少しだけ位置がズレて見えることがあります。この記事では、その位置ズレを CSS だけで直す scroll-margin-topscroll-padding-top の使い方を、実際にスクロールして試せるデモを交えて解説します。似ているようで役割が違う「scroll-margin」と「scroll-padding」の違い、各方向のプロパティ、scroll-snap との関係まで順番に見ていきます。

アンカーリンクで見出しが固定ヘッダーに隠れる理由

<a href="#section2"> のようなアンカーリンクを押すと、ブラウザは id="section2" を持つ要素の上端が、スクロール領域のちょうど一番上に来るようにスクロールします。このとき、ブラウザは「固定ヘッダーがそこに重なっている」ことを知りません。position: fixedposition: sticky のヘッダーは、あくまで他の要素の上に重ねて表示されているだけで、ジャンプ先の計算には関わらないためです。

その結果、目的の見出しは画面の一番上まで来るものの、その位置にはヘッダーが重なっているため、見出しがヘッダーの裏に潜り込んで見えなくなります。かつては透明な余白要素をアンカー用に挟んだり、JavaScript でスクロール位置を補正したりして対応していましたが、今は CSS の scroll-margin-top ひとつで解決できます。

scroll-margin-top でジャンプ先に余白を作る

scroll-margin-top は、その要素へスクロールして止まるときに、上側にどれだけ余白を空けて止めるかを指定するプロパティです。ジャンプ先の要素(見出しなど)に、固定ヘッダーの高さと同じか少し大きい値を指定します。ヘッダーの高さが 60px なら、次のように書きます。

style.css
/* ジャンプ先になる見出しに余白を持たせる */
h2[id],
h3[id] {
  /* 固定ヘッダー(60px)ぶん上に余白を空けて止める */
  scroll-margin-top: 60px;
}

大切なのは、この余白がスクロールして止まる位置の計算にだけ効くという点です。通常の margin-top のように見出しの周りに実際の余白ができるわけではなく、レイアウトの見た目は一切変わりません。あくまで「ジャンプで止まる位置を、指定したぶんだけ下にずらす」ための専用プロパティだと考えると分かりやすいです。値には px のほか、ヘッダーの高さに合わせやすい rem や、ビューポート基準の vh なども使えます。

スクロールして効果を確かめる

下のデモは、上部に固定ヘッダー(メニュー)があり、その中でスクロールできる小さなページです。メニューのリンクを押すと、対応する見出しへジャンプします。各セクションに scroll-margin-top を指定しているので、見出しがヘッダーの下にきちんと余白を空けて止まります。CSS タブの scroll-margin-top: 56px; の値を 0 に変えてから同じリンクを押すと、見出しがヘッダーの裏に隠れてしまう「ズレた状態」を再現できます。

<div class="page">
  <header class="page__header">
    <nav class="page__nav">
      <a href="#sec1">説明</a>
      <a href="#sec2">料金</a>
      <a href="#sec3">よくある質問</a>
    </nav>
  </header>
  <main class="page__body">
    <section id="sec1" class="section">
      <h2>説明</h2>
      <p>上のメニューをクリックすると、その見出しへジャンプします。見出しには scroll-margin-top を付けているので、固定ヘッダーに隠れません。</p>
    </section>
    <section id="sec2" class="section">
      <h2>料金</h2>
      <p>ジャンプ先の見出しが、ちょうどヘッダーの下に余白を空けて止まります。scroll-margin-top を消すと、ヘッダーの裏に隠れてしまいます。</p>
    </section>
    <section id="sec3" class="section">
      <h2>よくある質問</h2>
      <p>この余白はスクロール位置の計算にだけ効くので、見た目のレイアウトはそのままです。</p>
    </section>
  </main>
</div>
.page {
  height: 260px;              /* デモ用にスクロールできる高さ */
  overflow-y: auto;           /* この中でスクロールさせる */
  position: relative;
  scroll-behavior: smooth;    /* ジャンプを滑らかに */
  font-family: sans-serif;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.page__header {
  position: sticky;           /* 上に貼り付く固定ヘッダー */
  top: 0;
  height: 48px;
  background: #1f2937;
  z-index: 1;
}

.page__nav {
  display: flex;
  gap: 16px;
  height: 100%;
  align-items: center;
  padding: 0 16px;
}

.page__nav a {
  color: #fff;
  text-decoration: none;
  font-size: 14px;
}

.section {
  padding: 16px;
  border-bottom: 1px solid #eee;
  /* ↓ ここがポイント。ヘッダーの高さ(48px)ぶん余白を空けて止める */
  scroll-margin-top: 56px;
}

.section h2 {
  margin: 0 0 8px;
  font-size: 18px;
  color: #2563eb;
}

.section p {
  margin: 0;
  line-height: 1.8;
  color: #333;
}

/* 最後のセクションを高くして、上の見出しまでスクロールできるようにする */
#sec3 {
  min-height: 200px;
}
Preview

scroll-margin と scroll-padding の違い

同じ位置ズレを直すプロパティに scroll-padding-top もあります。どちらもヘッダーぶんの余白を作る点は同じですが、余白を付ける相手が違いますscroll-marginスクロールして止まる側の要素(見出しなどジャンプ先)に付ける余白です。一方 scroll-paddingスクロールするコンテナ側overflow を持つ親要素や、ページ全体なら html)に付ける、内側の余白です。

プロパティ付ける相手意味
scroll-marginスクロール先の要素(見出しなど)その要素を止めるときに空ける外側の余白
scroll-paddingスクロールするコンテナ(html など)スクロール領域の内側に確保する余白

固定ヘッダーの位置ズレ対策としては、どちらを使っても同じ見た目にできます。ただし考え方が異なります。scroll-padding-top はコンテナ側に一度だけ指定すれば、その中のすべてのジャンプ先に一律で余白が効くため、ページ全体で固定ヘッダーを避けたいときに向いています。多くの場合はこちらがシンプルです。次のように、スクロールするコンテナ(ページ全体なら html 要素)に指定します。

style.css
/* ページ全体のスクロールに対して、上に60pxの余白を確保する */
html {
  scroll-padding-top: 60px;
}

これだけで、ページ内のどのアンカーへジャンプしても、上に 60px の余白が空いた位置で止まるようになります。ジャンプ先ごとに個別のスタイルを書く必要がありません。逆に scroll-margin は、特定の要素だけ止まる位置を調整したいときに便利です。たとえば「この見出しだけもう少し下で止めたい」といった細かい制御は、その要素に scroll-margin-top を指定して行います。

上下左右それぞれの方向を指定する

どちらのプロパティにも、marginpadding と同じように方向ごとの個別プロパティと一括指定があります。固定ヘッダー対策では上方向の -top だけをよく使いますが、横スクロールのカルーセルなどでは -left が活躍します。

プロパティ効果
scroll-margin-topスクロール先要素の上側の余白(固定ヘッダー対策の定番)
scroll-margin-bottom下側の余白
scroll-margin-left / scroll-margin-right左・右の余白(横スクロール時に使う)
scroll-margin四方向をまとめて指定する一括指定
scroll-padding-topコンテナ上側の内余白(固定ヘッダー対策の定番)
scroll-paddingコンテナの四方向をまとめて指定する一括指定

一括指定は margin と同じ書き方で、scroll-margin: 60px 0; のように「上下 / 左右」の順で複数の値を渡せます。scroll-padding も同様です。ふだんの固定ヘッダー対策では -top 一つで足りることがほとんどなので、まずは scroll-padding-topscroll-margin-top を覚えておけば十分です。

scroll-snap と組み合わせて止め位置を整える

scroll-paddingscroll-margin は、もともと scroll-snap(スクロールが決まった位置でピタッと吸着する仕組み)とセットで設計されたプロパティです。scroll-snap-type を使ったカルーセルやフルスクリーンのスライドでも、各スライドがスナップして止まる位置を、この2つで微調整できます。

たとえば横スクロールのカルーセルで、左端にカテゴリ表示などの固定の帯がある場合、scroll-padding-left をコンテナに指定すると、スライドがその帯に隠れない位置でスナップします。縦方向のスライドで上部にヘッダーがあるなら scroll-padding-top を使う、という具合です。アンカーリンクの位置ズレ対策も、スナップ位置の調整も、「スクロールが止まる基準位置をずらす」という同じ仕組みの応用だと理解しておくと、両方を無理なく使い分けられます。scroll-snap 自体の使い方は別記事で詳しく解説しています。

余白が効かないときに確認すること

scroll-padding を付ける要素を間違えている

scroll-padding実際にスクロールしている要素に指定しないと効きません。ページ全体がスクロールしているなら html に、特定のボックス内でスクロールしているならその overflow: auto(や scroll)を持つ要素に付けます。見出しなどのジャンプ先に scroll-padding を付けても効果はないので、その場合は scroll-margin を使う、と切り分けて考えると混乱しません。

ジャンプ先が入れ子のスクロール領域にある

ジャンプ先の要素が、overflow を持つ別のコンテナの中にあると、ブラウザはページ全体だけでなくその内側のコンテナもスクロールさせようとします。htmlscroll-padding-top を付けたのに効かないときは、ジャンプ先が入れ子のスクロール領域に入っていないかを確認し、必要ならそのコンテナ側にも余白を指定します。要素ごとに確実に効かせたい場合は、ジャンプ先に scroll-margin-top を直接付けるのが手堅い方法です。

ヘッダーの高さと余白の値が合っていない

余白の値が固定ヘッダーの実際の高さより小さいと、見出しがまだ少しヘッダーに隠れます。反対に大きすぎると、見出しの上に不自然な空きができます。ヘッダーの高さが画面幅で変わるレスポンシブなサイトでは、値を固定の px にしていると、ある画面幅でズレることがあります。ヘッダーの高さを --header-height のような CSS 変数で管理し、scroll-padding-top: var(--header-height); のように参照すると、高さの変更に追従しやすくなります。

まとめ

アンカーリンクで見出しが固定ヘッダーに隠れる位置ズレは、scroll-margin-topscroll-padding-top でヘッダーの高さぶんの余白を作れば直せます。ジャンプ先の要素側に付けるのが scroll-margin、スクロールするコンテナ側に付けるのが scroll-padding という違いを押さえておきましょう。ページ全体で一律に避けたいなら htmlscroll-padding-top、特定の要素だけ調整したいなら要素に scroll-margin-top、と使い分けるのがおすすめです。どちらも scroll-snap のスナップ位置の調整にも使える、スクロールの止め位置をずらすための共通の仕組みです。

参考ページ