2021-1-14
position: fixedなヘッダーがアンカーリンク先の要素と被る問題の対策
追記:2021-1-14
CSSの進化によって面倒が少なくなったので修正した
結論
- スムーススクロールはJSではなくCSSの
scroll-behavior: smooth
を使用する。 - ヘッダーに被らないようにするには
- Safari or IE11対応必要
:target
疑似セレクタを使用する
- Safari and IE11対応不要
scroll-margin-top
を使用する
- Safari or IE11対応必要
scroll-behavior
ルート要素に scroll-behavior: smooth
を指定することで、スクロール挙動がスムースになる。スクロール速度に関しては自動で設定されるため関与できない。「もうちょっとスムーススクロール速くしたい」と言われたら頑張って説明しよう。
JSでスムーススクロールをやってしまうと、JSとCSS両方でヘッダー分ずらす処理を入れないといけないため二重管理となってしまいつらい。これを避けるために極力このプロパティを使うべき。
ヘッダーに被らないようにするには
scroll-margin-top
というセレクタがあり、これを指定するとCSS制御のスクロールの際に指定した値分だけずらしてくれる。
:root {
--headerHeight: 100px;
}
:target {
scroll-margin-top: var(--headerHeight);
}
上のようにすれば、これだけでヘッダーが被らないようになる。便利。
ただし、SafariとIE11は非対応のためこれら2つに対応する場合。
:target:before {
content: '';
display: block;
height: var(--headerHeight);
margin-top: var(--headerHeight * -1);
visibility: hidden;
}
のようにする必要がある点に注意。
以下過去記事
position: fixed
なヘッダーがある場合、何も対策していないとスムーススクロールの際にヘッダーとスクロール先の要素が被ってしまう。
そういう場合に一般的には以下のようにヘッダーの高さ分スクロール位置をずらすことで対策をしている。
var duration = 300
var href = $(this).attr('href')
var $target = $(href === '#' || href === '' ? 'html' : href)
if ($target.length === 0) return false
var headerHeight = 100
// ヘッダーの高さ分スクロール位置をずらすことで対策
var position = $target.offset().top - headerHeight
$('html,body').stop().animate({ scrollTop: position }, duration, 'swing')
ただこの対策しかしていない場合、<a href="./about.html#message">
のようにページ外からのアンカーリンクに関してはヘッダーがだだ被りしてしまう。ページ外からの遷移の場合は前述の対策が適用されないので当然。
対策方法
アンカーリンクのターゲットとなる要素に対して、以下の CSS を適用する。
:before {
content: '';
display: block;
margin-top: -100px;
padding-top: 100px;
}
margin-top
及びpadding-top
の値はヘッダーの高さ。
このスタイルの与え方は以下の2パターンある。
:target
を利用する方法
Demo の Example01 の方法
:not(.js-scroll-target):target:before {
content: '';
display: block;
margin-top: -100px;
padding-top: 100px;
}
:target
はハッシュの対象となっている要素。こうしておけばページ外からのリンクの場合だけ対象の要素に対してスタイルを適用することが出来る。
:not(.js-scroll-target)
は、ページ外からハッシュ付きで遷移した後さらにスムーススクロールをした場合にヘッダーがずれてしまうことに対する対策。スムーススクロールをする前のタイミングで$target.addClass('js-scroll-target')
とかしてこのスタイルが適用されないようにするとずれがなくなる。
既存サイトの場合、出来るだけ今あるソースコードに手を入れずに修正したい場合が多々あるのでそういう場合この方法が便利。
普通にクラスを与える方法
Demo の Example02 の方法
.u-anchorTarget:before {
content: '';
display: block;
margin-top: -100px;
padding-top: 100px;
}
すごい普通な感じ。このu-anchorTarget
クラスを愚直にアンカーリンクの対象となる要素に与える。
クラスを与える手間はあるが、スムーススクロールの際にヘッダーの高さ分 JS でスクロール位置をずらしたり、:target
の方法のようにスクロール前に特定のクラスを付与したりする必要がない。
ヘッダー被りの対策コードがこのスタイルただ1点に集中するため後から見た際にコードの意図がわかりやすい…と思う。