ヘッダーの高さを勘で書かない:ResizeObserver で実測して戻す
ナビの折り返し、遅れて届く Web フォント、内容の長さの変化で、CSS に書いた定数はすぐずれる。ResizeObserver で要素そのものを観測し、実測値を変数に書き戻す。
ヘッダーの高さは? ナビは折り返す? 目次はどれくらいの幅? どれも CSS に大体の値は書けますが、正確には書けません。しかも値は動きます。フォントの読み込み、ウィンドウのリサイズ、内容の長さの変化です。
window の resize では足りない
window.addEventListener('resize') が拾えるのはビューポートのサイズという一種類の変化だけです。フォント到着後の行高の変化、details の展開、同じリサイズ中に非同期で入ってくる内容は抜け落ちます。さらにドラッグ中は毎フレーム発火し、そのハンドラで getBoundingClientRect() を読むとスタイル書き込みと交互になり、同期レイアウトを強制します。
要素そのものを観測する
ResizeObserver が観測するのは要素です。理由を問わず、寸法が変わればコールバックが来ます。
const header = document.querySelector('[data-site-header]');
if (header) {
const observer = new ResizeObserver(() => {
const height = Math.round(header.getBoundingClientRect().height);
document.documentElement.style.setProperty('--anchor-offset', height + 16 + 'px');
});
observer.observe(header);
}
コールバックは書き込むだけで、他の要素は読みません。観測対象の寸法に影響する変更を書き込むと、ブラウザは ResizeObserver loop completed with undelivered notifications を投げます。これは「ループを作った」というプラットフォームからの通知です。
実務的な 2 点
- 書き込む前に丸める。 サブピクセルの高さはコールバックごとに揺れ、
--anchor-offsetが毎秒何十回も変わり、アンカーの補正まで揺れます。整数化してから、同じ値のうちは書きません。 - 未対応ブラウザは単に飛ばす。 ここでの用途はすべて補正で、無くてもページは動きます。ポリフィルも、欠落時のエラー通知も入れません。
使わない方がよい場面
CSS で表せる寸法は CSS で書きます。height: auto、aspect-ratio、clamp()、グリッドの自動配置があります。JavaScript で測って書き戻すのは、CSS が既にやっていることを模倣し、レイアウトの往復を 1 回増やすだけです。ResizeObserver の居場所はCSS では表せない要素間の関係です。ヘッダーの高さがアンカーのオフセットを決め、内容の幅がレール型の目次を出すかどうかを決めます。
測れるなら勘で書かない。CSS が計算できるなら測らない。

コメント
…