ページ上部にヘッダを固定表示するデザインは、今ではすっかり定番になりました。スクロールしてもナビゲーションが常に見えているので便利なのですが、私が最初にこれを実装したとき、ひとつ困ったことが起きました。ページ内リンクを踏むと、飛んだ先の見出しが固定ヘッダの裏に隠れてしまうのです。
リンク先の位置がヘッダの高さ分だけ上にずれてしまい、肝心の見出しが読めない。ページ内リンクを多用しているサイトほど気になる、地味だけど厄介な問題です。
この記事では、固定ヘッダによるページ内リンクの位置ずれを解消する方法を紹介します。この記事を書いた2017年当時はjQueryでスクロール量を補正するのが主流でしたが、今はCSSだけであっさり解決できるようになりました。そちらを主軸に、JavaScriptでの制御が必要な場合の書き方も残しておきます。
まずは前提となるHTML
以下のように、固定ヘッダとページ内リンクがある構成を例に説明します。ヘッダの高さは「100px」としておきます。
<div id="header">
ヘッダー
</div>
<div id="content">
<a href="#link01">リンク元</a>
<div id="link01">リンク先</div>
</div>
ヘッダのCSSはこんな感じです。
#header {
width: 100%;
height: 100px;
position: fixed;
left: 0;
top: 0;
background: #F00;
}
このままだと、#link01に飛んだときにリンク先が固定ヘッダの裏に潜り込んでしまいます。ここを直していきます。
今の定番はCSSだけで解決する
現在は、飛び先の要素にscroll-margin-topを指定するだけで済みます。これはスクロールして要素に到達したとき、その要素の上に余白を確保してくれるプロパティで、まさに固定ヘッダのために用意されたような機能です。
#link01 {
scroll-margin-top: 100px; /* ヘッダの高さと同じ値 */
}
これだけで、リンク先がヘッダの高さ分だけ下にずれて表示され、見出しがヘッダに隠れなくなります。MDNでも、固定ヘッダに隠れないようにアンカー先へ余白を与える用途として紹介されている方法です。(参考:MDN scroll-margin-top)
飛び先の見出しがたくさんある場合は、対象をまとめて指定してしまうと楽です。
:target {
scroll-margin-top: 100px;
}
要素ごとに書きたくないときは scroll-padding-top
飛び先ひとつずつに指定するのが面倒なら、スクロールコンテナ側にまとめて余白を持たせる方法もあります。html要素にscroll-padding-topを指定すると、ページ全体のスクロール位置にヘッダ分のオフセットがかかります。
html {
scroll-padding-top: 100px; /* ヘッダの高さ分の余白 */
}
scroll-padding-topはスクロール領域の内側に余白を作るプロパティで、固定ヘッダなどに隠れる領域を除外する用途で使えます。(参考:MDN scroll-padding-top)これなら飛び先の要素側に手を入れずに済むので、既存サイトへの後付けにも向いています。
ついでにスクロールも滑らかに
かつてはページ内リンクをスルッと移動させるためにjQueryのアニメーションを使っていましたが、これもCSSだけでできます。htmlにscroll-behavior: smoothを足すだけです。
html {
scroll-behavior: smooth;
scroll-padding-top: 100px;
}
これでクリック時にヌルッと移動し、しかもヘッダに隠れない、という状態がCSSだけで完成します。scroll-margin-top・scroll-padding-top・scroll-behaviorのいずれも主要ブラウザで広く使えるので、まずはこのCSS方式で足りるケースがほとんどだと思います。
JavaScriptで制御したい場合(従来のjQuery方式)
スクロール量を細かく調整したい、移動時に別の処理も走らせたい、といったケースではJavaScriptで制御する余地があります。以前主流だった、jQueryで飛び先の座標からヘッダの高さ分を引くやり方も載せておきます。
$(function () {
var headerHight = 100; //ヘッダの高さ
$('a[href^="#"]').click(function(){
var href = $(this).attr("href");
var target = $(href == "#" || href == "" ? 'html' : href);
var position = target.offset().top - headerHight; //ヘッダの高さ分位置をずらす
$("html, body").animate({scrollTop: position}, 550, "swing");
return false;
});
});
飛び先の座標を取得し、そこからヘッダの高さを引いた位置までスクロールさせています。動きは作れますが、jQueryへの依存が増えますし、今はCSSだけで同じことができるので、あえてJSを使うのは「移動と同時に何かしたい」ような場合に絞るのがおすすめです。
まとめ
固定ヘッダとページ内リンクの相性の悪さは、昔からある定番の悩みです。振り返ると解決の手段もずいぶん変わりました。
- 飛び先の要素に
scroll-margin-topを指定するのが今のいちばん手軽な方法。 - 要素ごとに書きたくなければ
htmlにscroll-padding-topでまとめて対応。 - 滑らかな移動は
scroll-behavior: smoothで追加できる。 - 移動と同時に他の処理も走らせたいときだけ、jQuery(JavaScript)での制御を検討する。
まずはCSSで試して、それで足りなければJSに手を伸ばす。この順番で考えると、余計なスクリプトを抱え込まずにスッキリ解決できると思います。