💻

[CSS] ページ内リンクのスクロール位置のズレをなくす方法

2020/04/12に公開

グローバルナビを position:fixed で画面最上部に固定しているとき、ページ内リンクのスクロール位置がズレて困ったことはありませんか?僕はあります。

CSSで解決する方法を示しますので、ぜひ参考にしてください👍

結論

前衛的な方法と保守的な方法の2つがあります。

前衛的な方法

scroll-padding-top プロパティ を使えばシンプルに解決できます。

html {
  scroll-padding-top: 70px;
}

これだけで、スクロール位置が 70px ずれてくれます🙌革命的〜!

ただし、2020/05/14時点でIEとSafari(Mac/iOSとも)が非対応なので、特にiPhoneユーザーを意識する場合は採用するのが難しいのが現状です。

保守的な方法

scroll-padding-top を使わない場合、以下のようなCSSで解決できます。

body {
  padding-top: 70px;
}
h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
  position: relative;
}
h1[id]:before, h2[id]:before, h3[id]:before, h4[id]:before, h5[id]:before, h6[id]:before {
  content: '';
  display: block;
  height: 70px;
  margin-top: -70px;
}

なお、下図のように見出しタグの hover でリンクを表示させたい場合の当たり判定の調整方法については こちらの記事 で解説していますので、よろしければあわせてご参照ください。

移行は、こちらの方法について詳細に解説します。

具体例

一応、よく分からないという人のために具体例を示しながら順を追って説明してみます。

position:fixed なグローバルナビがあると何が起こるか

まず、例として以下のようなBootstrapを使ったHTMLを書いてみましょう。(コピペでそのまま動かせます)

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
  <nav class="navbar navbar-dark bg-dark fixed-top">
    <div class="container">
      <a class="navbar-brand" href="">Navbar</a>
    </div>
  </nav>
  <div class="container">
    <p>先頭のコンテンツ</p>
  </div>
</body>
</html>

fixed-top クラスを指定しているので、このnavbarは position:fixed でページ最上部に固定されます。

これをブラウザで見てみると、以下のような表示になります。

先頭のコンテンツ という文字が見当たりませんね🤔

デベロッパーツールでnavbarを削除してみましょう。

navbarの下に隠れていました。

つまり、 position:fixed なグローバルナビがあると、bodyの先頭部分が隠されてしまうというわけです。

先頭が隠されてしまう問題を、bodyに padding-top を設定して解決

この問題を解決するには、以下のようにbodyに padding-top を設定してあげればよいです。

body {
  padding-top: 70px;
}

この例で設定した 70px という数字は、navbarの高さ 56px にいくらか足して先頭に適度に空白ができるように調整したものです。デザインに応じて適当に設定しましょう。

bodyに padding-top があっても、ページ内リンクのスクロール位置は変わらない問題

さて、このページにページ内リンクを導入したいとしましょう。

HTMLを以下のように修正して、 id="h1" を設定した見出しの位置までページ内リンクできるようにしてみます。

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
  <style>
    body {
      padding-top: 70px;
    }
  </style>
</head>
<body>
  <nav class="navbar navbar-dark bg-dark fixed-top">
    <div class="container">
      <a class="navbar-brand" href="">Navbar</a>
      <ul class="navbar-nav mr-auto">
        <li class="nav-item"><a href="#h1" class="nav-link">見出しへジャンプ</a></li>
      </ul>
    </div>
  </nav>
  <div class="container">
    <p>先頭のコンテンツ</p>
    <br><br><br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br><br><br>
    <h1 id="h1">見出し</h1>
    <br><br><br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br><br><br><br>
  </div>
</body>
</html>

動かしてみると…

見出し が表示されません。

navbarを消してみると、

案の定、下に隠れていました。

考えてみれば当たり前のことですね。bodyの padding-top によってページの先頭にはnavbarの分だけ余白を作りましたが、ページ途中の要素の位置まで飛んだときにはその要素が画面の最上に来る位置がスクロール位置になるからです。

見出しの上に見えない余白をつければ解決できる

このページ内リンクのスクロール位置ズレ問題、jQueryプラグインなどでページ内リンク時にちょっとだけ余分にスクロールしてくれるようにするといった解決法も考えられますが、実はCSSだけで対応できますよというのがこの記事の内容です。

最初に書きましたが、以下のCSSで解決できます。

body {
  padding-top: 70px;
}
h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
  position: relative;
}
h1[id]:before, h2[id]:before, h3[id]:before, h4[id]:before, h5[id]:before, h6[id]:before {
   content: '';
   display: block;
   height: 70px;
   margin-top: -70px;
 }

id が設定されているすべての見出しタグに対し、 70px 分の「見えない余白」を作っています。

これにより、ページ内リンク時のスクロール位置が「見えない余白の先頭」になるので、上手くnavbarからはみ出てくれるというわけです。

動かしてみましょう。

バッチリですね!

まとめ

  • グローバルナビを position:fixed で画面最上部に固定しているときにページ内リンクのスクロール位置がズレる問題はCSSだけで解決できる
GitHubで編集を提案

Discussion