Sticky Navでアンカーリンクが隠れる問題をインラインCSS数行でスッキリ解決する方法

ドキュメント(ブログ/ウィキ/ガイド)で脚注リンクやTOC(目次)をクリックすると、#some-id に移動し、該当する id を持つ要素がビューポートの最上部に揃えられます。

しかし、ページ上部に position: sticky(または fixed)で固定されたナビゲーションバーがあると、スクロールは最上部に合わせられるものの、実際のコンテンツはナビの後ろに隠れてしまうという問題が発生します。

フロントエンドではよくある課題ですが、バックエンド/フルスタックの立場からは「リンクは正しく遷移したのに内容が見えない」など、困惑する場面が多いです。

ここでは、私が最も頻繁に使う 「問題が起きるページにインラインCSSを数行追加」 という手法(最もシンプルで即効)を中心に整理します。


なぜこうなるのか?

ブラウザのデフォルト動作は次のとおりです。

  • URL が .../page#target のように変わると
  • ブラウザは id="target" 要素を探し
  • その要素の開始点が スクロールコンテナの開始(通常はビューポート上部) に来るようにスクロールします。

しかし sticky nav はビューポート上に 重なって 描画されるため、結果として要素が nav の下に隠れてしまいます。

リンクの target が sticky nav によって隠れるケース画像


最も簡単な解決策:scroll-margin-top(要素側に余白を与える)

アンカーでスクロールされる「表示される」要素に scroll-margin-top を付与すると、ブラウザはその要素を最上部ではなく 「余白分だけ下」 に位置させようとします。

インラインCSSで最小限に適用(推奨)

問題のあるページの <head> や該当ページのテンプレートに、以下のようにだけ入れても解決するケースが多いです。

<style>
  :root { --sticky-nav-h: 64px; } /* nav の実際の高さに合わせる */
  [id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>
  • --sticky-nav-h:sticky nav の高さ
  • + 12px:少し隠れないように余裕を持たせる(好み)

[id] 全体に適用すると「このページでアンカーで移動されるすべての対象」が自動で補正されます。 ページ全体に広く適用するのが負担に感じる場合は、以下のように対象範囲を絞るとよいです。

範囲を絞ってより安全に

例:本文領域のみに適用

<style>
  :root { --sticky-nav-h: 64px; }
  .article [id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>

例:TOC が主に h2/h3 へ移動する場合

<style>
  :root { --sticky-nav-h: 64px; }
  .article h2[id], .article h3[id] {
    scroll-margin-top: calc(var(--sticky-nav-h) + 12px);
  }
</style>

さらに便利なオプション:scroll-padding-top(コンテナ側のデフォルトパディング)

scroll-padding-top は「スクロールスナップ/フラグメント移動」などで、スクロールコンテナが 上部の安全領域 を持つようにヒントを与えます。 実務では scroll-margin-top の方が直感的ですが、組み合わせておくとレイアウトに応じて安定性が向上するケースがあります。

<style>
  :root { --sticky-nav-h: 64px; }
  html { scroll-padding-top: calc(var(--sticky-nav-h) + 12px); }
</style>
  • ページ単位で「全アンカー移動補正」を素早く行いたいときに便利です。
  • ただし「特定の要素だけ」を細かく制御したい場合は scroll-margin-top の方が扱いやすいです。

レガシー/互換性用の古典的トリック:::before で擬似オフセットを作る

昔から使われている手法です。アンカーターゲット要素の前に見えないブロックを作り、実際の位置を nav の下に下げます。

<style>
  :root { --sticky-nav-h: 64px; }

  .anchor-target::before {
    content: "";
    display: block;
    height: calc(var(--sticky-nav-h) + 12px);
    margin-top: calc(-1 * (var(--sticky-nav-h) + 12px));
    visibility: hidden;
    pointer-events: none;
  }
</style>

使用例:

<h2 id="install" class="anchor-target">설치</h2>
  • 長所:古い方式なので「原理上」確実に動く
  • 短所:クラス付与が必要で、要素選択/構造を少し気にする必要がある

最近はまず scroll-margin-top を試し、特定環境でうまくいかない場合に ::before トリックを検討する流れがスッキリします。


JS でも解決可能だが、「ページ単位のインライン CSS」 が勝るケース

もちろん JavaScript で scrollIntoView() 後に window.scrollBy(0, -navHeight) などで補正することもできます。 しかしこの場合は CSS の方が優れています。

  • 文書/静的コンテンツページ(脚注/TOC が多いページ)
  • フレームワーク/ルーティングレベルを変更せずに「そのページだけ」を素早く解決したいとき
  • メンテナが FE 専門ではない可能性が高いとき

インライン <style> 数行は 依存性がなく、デバッグも簡単で、ロールバックも容易 なので、文書性ページに特に適しています。


実務チェックリスト

  • sticky nav の高さを正確に把握する
  • レスポンシブで高さが変わる場合は、ブレークポイントごとに --sticky-nav-h を変更するだけで OK
  • 適用範囲を広げすぎない
  • [id] 全体適用は便利だが、ページ構造に応じて .article [id] などで限定することを推奨
  • 「余裕(+8〜16px)」を与えると感覚が良くなる
  • ちょうど nav の高さだけだと、心理的に貼り付いて見えるケースが多い

結論:私が最も頻繁に使う一手

問題が起きるページに以下をインラインで入れて完了です。

<style>
  :root { --sticky-nav-h: 64px; }
  .article [id] { scroll-margin-top: calc(var(--sticky-nav-h) + 12px); }
</style>

これで TOC/脚注/ディープリンクまで一括で解決できるケースがほとんどです。リンクが nav の後ろに隠れる問題を解決したい開発者の方に、ぜひ参考にしていただければと思います。


関連記事 - 画像タグに width と height を明示する理由と効果 - バックエンドエンジニアでも最低この程度は知っておくべき - フロントエンド JS メソッド、モジュール Best 5