アンカーがヘッダーに隠れる:scroll-padding-top を一箇所に

固定ヘッダーはアンカーの着地点を隠す。見出しごとの scroll-margin は必ず漏れる。正解はスクロールコンテナの scroll-padding-top で、実際のヘッダー高さを ResizeObserver から戻す。

固定ヘッダーはアンカーの着地点を飲み込む。目次の見出しをクリックするか、# 付きの共有リンクを開くと、見出しがちょうどヘッダーの下に着地する。

素直な直し方と、その漏れ方

最初に思いつくのは、すべての見出しに scroll-margin-top を付けることです。

h2, h3 { scroll-margin-top: 72px; }

動きますし、レビューも通ります。その後で誰かが脚注を足し、小さなラベルに id を付け、details を開いたときだけ現れるアンカーを足す。オフセットは定数のままで対象だけが増えるので、一つ漏らせば元に戻ります。

正しい置き場所はスクロールコンテナ

scroll-padding-top はスクロールする要素に書き、その中のアンカーをまとめて面倒みます。

html {
  scroll-padding-top: var(--anchor-offset);
}

--anchor-offset の既定値はヘッダーの高さ + 余白 1 段分。デスクトップなら足ります。スマートフォンでは足りません。

定数には分からない半分

狭い画面ではナビが折り返し、ヘッダーが 1 行から 2 行になり、実際の高さは --header-height より 1 行分高くなります。CSS 側からは分からないので、書き込んだトークンはすぐ不足し、見出しは再びヘッダーの下へ潜ります。

CSS には計算できずブラウザには測れる値は、ブラウザ側の小さなスクリプト(src/lib/anchor.ts)に任せています。ResizeObserver でヘッダーの getBoundingClientRect().height を測り、余白 1 段を足して --anchor-offset に書き戻す。フォントの読み込み、ウィンドウのリサイズ、ナビの折り返し —— 寸法が変わるたびに測り直します。

JavaScript でスクロールしない理由

window.scrollTo で目標位置を自分で計算することもできます。その場合、滑らかなスクロール、スクロール中に画像が読み込まれて起きるレイアウトずれ、クリック処理を通らないキーボードの Tab 移動という 3 つも自分で抱えることになります。ブラウザはどれも正しく処理済みです。作り直せば境界条件が 3 つ増えるだけです。

レイアウトの知識はレイアウトに置く。JavaScript は CSS が測れない 1 つの数値だけを渡す。

代償はインライン化できない小さなスクリプトです。ResizeObserver が無い環境では既定のトークンに静かに戻ります。アンカーは使えたままで、補正だけが定数に戻るだけです。

← 記事一覧に戻る

コメント

…