ナビが溢れたら CSS だけでドロワーに切り替える(scroll-timeline で溢れ検知)
ナビのリストに scroll-timeline を付け、「スクロールできる=溢れている」時だけ有効になるタイムラインでカスタムプロパティを --true に切り替え、スタイルクエリでナビとドロワーボタンを出し分ける。
どんな時に使う?
ヘッダーのグローバルナビを「画面幅 ○px 以下ならハンバーガー」とブレイクポイントで切り替えると、CMS で項目が増えたり文言が長くなったりした時に、ブレイクポイントより広い幅でもナビが溢れて崩れる。この方法は「実際に収まらなくなった瞬間」を CSS が検知して切り替えるので、項目数や文字数が変わっても破綻しない。JS の ResizeObserver も要らない。
仕組み
- ナビのリストを
overflow-inline: autoにして、scroll-timeline: --nav-list inlineを付ける。スクロールタイムラインは「実際にスクロールできる時」だけアクティブになり、溢れていない時は非アクティブになる。 - ヘッダー側で
timeline-scopeを使ってそのタイムライン名を親まで引き上げ、animation-timeline: --nav-listでキーフレームを紐付ける。キーフレームは最初から最後までカスタムプロパティを--trueにするだけ。 - タイムラインが非アクティブ(溢れていない)ならアニメーションは効かず、初期値の
--falseのまま。溢れた瞬間だけ--trueになる。これを「溢れフラグ」として使う。 - 子要素で
@container style(--flag: --true)を書き、ナビ・お問い合わせリンク・ドロワーボタンの表示を切り替える。
コード
HTML の骨組み(ロゴ、ナビ、右端にドロワーボタンとお問い合わせリンクを重ねて置く列):
html
<header class="site-header">
<a class="logo" href="/">Logo</a>
<nav aria-label="グローバルナビゲーション">
<ul class="nav-list">…</ul>
</nav>
<div class="end-column">
<button class="drawer-button" type="button" aria-label="メニュー">…</button>
<a class="contact-link" href="#">お問い合わせ</a>
</div>
</header>css
/* タイムラインがアクティブ(=溢れている)間だけ --true になる */
@keyframes header-observe {
from, to { --header-overflowing: --true; }
}
.site-header {
--header-overflowing: --false;
display: grid;
/* 中央のナビ列は 0 まで縮めるので、狭くなるとリストが溢れる */
grid-template-columns: minmax(max-content, 1fr) minmax(0, auto) minmax(max-content, 1fr);
timeline-scope: --nav-list; /* 子のタイムライン名を親から参照可能にする */
animation: header-observe 1ms both;
animation-timeline: --nav-list;
}
.nav-list {
display: grid;
grid-auto-flow: column;
overflow-inline: auto;
scroll-timeline: --nav-list inline;
@container style(--header-overflowing: --true) {
visibility: hidden; /* display: none にすると溢れが消えてフラグが戻り、無限ループする */
}
}
.end-column {
display: grid;
& > * { grid-area: 1 / 1; justify-self: end; } /* ボタンとリンクを同じマスに重ねる */
}
.contact-link {
@container style(--header-overflowing: --true) { visibility: hidden; }
}
.drawer-button {
@container style(--header-overflowing: --false) { visibility: hidden; }
}(元の投稿は @scope (.scoped.site-header) to (.scoped) で囲んでコンポーネント内に閉じている。@scope の使い方は @scope の「to (.scoped)」でコンポーネントのスタイル漏れを止める を参照。)
注意点・対応状況
- 隠すのは必ず
visibility: hidden。display: noneだとナビが消えて溢れが解消し、フラグが--falseに戻ってまた表示され、を繰り返す。visibility: hiddenなら場所は確保されたまま溢れ判定が続き、支援技術からも隠れる。 - 必要な機能はスクロール駆動アニメーション(
scroll-timeline/animation-timeline/timeline-scope)とカスタムプロパティのスタイルクエリ。投稿者によると Firefox は 159 から動く。未対応ブラウザではフラグが--falseのままなのでナビが常に表示される(溢れたら横スクロールになる)。フォールバックとして許容できるか確認する。 - ドロワー本体(開いた時のメニュー)は別途 popover や
<dialog>で用意する。この技は「どちらを見せるか」の切り替えだけ。 prefers-reduced-motionでアニメーションを全部止めるリセット CSS を入れていると、このフラグ用アニメーションまで止まって切り替わらなくなる。除外しておく。