diff --git a/CLAUDE.md b/CLAUDE.md index cecb61c9512..b4903221195 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1228,12 +1228,31 @@ bootstrap-subset → theme-styles.styl の順で `/css/site.css` に連結する - **ヘッダーの部品の寸法を本文のグリッドに従属させない。** 検索窓の幅を サイドバー列に合わせれば揃うが、サイドバーが縦積みになる幅(1024px以下)で その根拠が消える - - **ヘッダーの帯は追従(sticky)しない。** 64px の帯が全ページで本文の上に - 常に乗ることになり、読んでいる面が狭くなる。写真をやめたぶん - `backdrop-filter` も要らなくなった + - **ヘッダーの帯は 1025px 以上で追従(sticky)する。1024px 以下では追従しない** + (#3220。#2877 の「追従しない」を PC だけ覆した)。読み進めてもロゴが左上に残り + 「どのサイトを読んでいるか」が切り替わるたびに分かる、長いトップページで検索に + 手が届く、が理由。他サイトは固定 3(Qiita 45px・note 65px・dev.to 56px)対 + 非固定 6(Zenn・はてな・Cloudflare・GitHub Blog・Stripe・Medium)で割れていて、 + どちらが通り相場とも言えない + - **モバイルは追従しない。** 64px は 375×667 の画面の 10% で、記事の面を優先する + (Slack の議論でも PC の話に収束した) + - **境界は目次が追従し始める 1025px と同じ。** 新しい境界を足さず、PC の読者は + 既に追従する部品のある画面を見ている + - **追従する帯は磨りガラス**(地を 90%、`backdrop-filter: blur(8px)`)。本文の上に + 置かれた箱ではなく本文の一部として読ませる。**90% は帯の下にいちばん暗いもの + (コードブロック `#2b2b2b`)が来ても `ink-mute` が AA を保つ値**(5.15。85% では + 4.65 で余裕が無い)。ぼかしの量は引き出しの背後(#2888)と同じ。`@supports` で + 囲い、ぼかしが効かないブラウザでは不透明のまま——透けるだけだと文字の下に + 本文が見えて読めない。線(1px)と影なしは変えない。下へ送ると隠れる型(Medium)は + JS が要り、送ると細くなる型は動きが増えるので採らない。2段組にして2段目だけ + 固定する案は、帯が1段なので分ける相手が無い + - **帯を追従させると3箇所が連動する。** 目次の `top`(帯の下から 24px)と + `max-height`、目次のスクロールスパイの基準線(`sidebar.ejs`。帯の下辺 + 32px。 + 帯の `getBoundingClientRect().bottom` を読むので幅の分岐を持たない)、 + `html { scroll-padding-top }`(目次のクリックとアンカーで見出しが帯の下に隠れない) - **サイドバーの目次は追従する**(#2909。以前は「固定サイドバーを置かないのと 同じ理由」と書いていたが、そちらはトライアルで入れた)。目次は - 読んでいる記事の中の移動手段で、帯のように全ページに常駐する部品ではない + 読んでいる記事の中の移動手段で、帯と同じ幅で追従する - **帯の中身は `.container` の内側に置く。** サイト名の左端がパンくず・本文と揃う。 下に開くパネル(ドロップダウン・検索のヒント)の位置の基準も `.header-bar` が持ち、 `top: 100%` が帯の下辺、`right: 0` が本文と同じ右端になる @@ -1333,7 +1352,7 @@ bootstrap-subset → theme-styles.styl の順で `/css/site.css` に連結する カーソルが乗っただけで本文全体がぼけるのは意図しない発火になる - 開いているかは **`:has()`** で拾う。引き金(`:checked` / `:focus-within`)が 帯の中にあるので、兄弟結合子では帯の外の面まで届かない - - **`position: fixed` にしない。** ヘッダーは追従しないので、開いたまま送られると + - **`position: fixed` にしない。** 引き出しが出る幅ではヘッダーは追従しないので、開いたまま送られると 引き出しは一緒に上へ動くのにぼかしだけ残る。帯の下から 100vh 分を 絶対配置で敷く - 出し入れは `display`。`visibility` では隠れている間も 100vh の箱が残り、 diff --git a/source/specials/styleguide/index.md b/source/specials/styleguide/index.md index fd85063a1bf..f28050f8d91 100644 --- a/source/specials/styleguide/index.md +++ b/source/specials/styleguide/index.md @@ -328,7 +328,7 @@ editable: true | 576px | 検索窓が虫眼鏡だけになり、フォーカスされたときだけ帯を覆って広がる。一覧カードのサムネイルが文字の上に積む | [すべての記事](/articles/) | | 768px | サイドバーの探索の枠が消える。2列で並べていた一覧が1列になる。コードと note が画面の端まで伸びる | [カテゴリ一覧](/categories/#categories) | | 992px | ヘッダーのナビが帯から降りて引き出しに入る。フッターが4列から2列になる | [トップページ](/) | -| 1025px | 本文とサイドバーが縦に積む。サイドバーの統計とグラフは消え、目次は本文の上の折りたたみに移る | [記事ページ](/articles/20260804a/) | +| 1025px | 本文とサイドバーが縦に積む。サイドバーの統計とグラフは消え、目次は本文の上の折りたたみに移る。ヘッダーの帯は追従をやめて本文と一緒に送られる | [記事ページ](/articles/20260804a/) | **中間の幅を作りません。** 段階を増やすと、どの幅で確かめた見た目なのかを追えなくなります。2列にするかどうかは列の内寸に中身が入るかで決めていて、たとえばカテゴリ一覧を「列の幅が足りたら自動で2列」にすると、幅 576px の画面でも1列 242px の2列になり、説明文とタグが入りません。列数は幅で決め打ちします。 diff --git a/themes/future/css-src/theme-styles.styl b/themes/future/css-src/theme-styles.styl index dab0cbfdff4..6420073b0eb 100644 --- a/themes/future/css-src/theme-styles.styl +++ b/themes/future/css-src/theme-styles.styl @@ -381,8 +381,6 @@ Header and header elements /* ヘッダーは1段・64px の帯 (#2877)。以前は 1500×360 の写真バナーの上に 80px の 中央タイトルを載せた 151px の塊で、全ページの先頭でそれだけ縦を使っていた。 縦に返した約90px のぶん、空いた横にサイト名・ナビ・検索を1行で並べる。 - **追従(sticky)はしない。** 64px の帯が全ページで本文の上に常に乗ることになり、 - 読んでいる面が狭くなる。サイドバーの目次は追従する (#2909)。 影は持たない (#2747)。下辺の 1px の罫線が本文との境目を作る */ .site-header { position: relative; @@ -392,6 +390,36 @@ Header and header elements /* 下に開くドロップダウンと検索のヒントパネルが本文の上に出る */ z-index: 999; } +/* 1025px 以上では帯が追従する (#3220)。読み進めてもロゴが左上に残り、検索が + 手の届く所にある。1024px 以下では追従しない——本文の面を優先する + (64px は 375×667 の画面の 10%)。境界は目次が追従し始める幅と同じで、 + PC の読者は既に追従する部品のある画面を見ている。見た目は変えない + (白地・1px の線・影なし)ので、送っても浮いた箱にはならない。 + scroll-padding は、目次のクリックや見出しのアンカーで見出しが帯の下に + 隠れないためのもの。帯の線に見出しが接すると詰まって見えるので 16px 空ける + (目次のスクロールスパイの線は帯の下から 32px なので、着地した見出しは線の内側) */ +@media (min-width: 1025px) { + .site-header { + position: sticky; + top: 0; + } + /* 追従する帯は磨りガラスにする。地を 90% にして下の本文をぼかして透かすと、 + 本文の上に置かれた箱ではなく本文の一部として読める。90% は帯の下にいちばん + 暗いもの(コードブロック #2b2b2b)が来ても ink-mute が AA を保つ値 + (4.5 に対して 5.15。85% では 4.65 で余裕が無い)。ぼかしの量は引き出しの + 背後と同じ 8px。ぼかしが効かないブラウザでは不透明のまま——透けるだけだと + 文字の下に本文が見えて読めない */ + @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) { + .site-header { + background: unquote('color-mix(in srgb, var(--surface-base) 90%, transparent)'); + -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(8px); + } + } + html { + scroll-padding-top: header-height + 16px; + } +} /* 帯の中身。左端はページ本文と同じ .container の内側に揃える。 下に開くパネルの位置の基準もここが持つ(top: 100% が帯の下辺になる) */ .header-bar { @@ -3256,16 +3284,16 @@ ul.tag-list { /* 左右の 4px は下の overflow-y: auto のため。スクロールコンテナになると 行のフォーカスの輪郭(2px + outline-offset 2px)が箱の縁で切られる */ padding: 0 4px; - /* スクロールに追従する (#2909)。帯は追従しないので、上端は画面の上から - 少しだけ空ける。サイドバーが本文の下に落ちる幅(1024px以下)では + /* スクロールに追従する (#2909)。帯も追従する幅 (#3220) なので、上端は + 帯の下から少しだけ空ける。サイドバーが本文の下に落ちる幅(1024px以下)では 目次自体を隠しているので、この指定は横並びのときだけ効く。 見出しが多い記事(h2〜h4 の中央値は10、最大62)では画面に収まらないので、 中でスクロールさせる。可視領域 800px で24項目まで入り、溢れるのは 全1,481本のうち68本(5%) */ position: sticky; - top: 24px; - max-height: unquote('calc(100vh - 48px)'); + top: header-height + 24px; + max-height: unquote('calc(100vh - ' + (header-height + 48px) + ')'); overflow-y: auto; } /* 見出しにも行と同じ左パディングを与えて、文字の左端を項目と揃える。 diff --git a/themes/future/layout/_partial/sidebar.ejs b/themes/future/layout/_partial/sidebar.ejs index e2dee0d4194..b230f6845d1 100644 --- a/themes/future/layout/_partial/sidebar.ejs +++ b/themes/future/layout/_partial/sidebar.ejs @@ -136,14 +136,19 @@ return a.compareDocumentPosition(b) & 4 ? -1 : 1; }); - // 画面の上から 32px の線を越えた見出しを「読み始めた」とみなす。 - // 帯は追従しないので、送った後の画面の上端が基準になる。 + // 帯の下辺から 32px の線を越えた見出しを「読み始めた」とみなす。 + // 帯は 1025px 以上で追従する (#3220) ので、基準は画面の上端ではなく帯の下辺。 + // 追従しない幅では送った帯が画面の外へ出るので、下辺は 0 で止める。 // // **線は「見出しから次の見出しまでの最小距離」より浅くする。** 目次の行を // クリックすると見出しが画面の上端(top 0)へ来るので、線が深いと次の見出しも // 線より上に入り、次の節が現在地になってしまう。CSS の値から出した最小距離は // h4 の直後に h4 が来る 46px(h3→h4 が 49px、h2→h3 が 72px) - var LINE = 32; + var header = document.querySelector('.site-header'); + var line = function () { + var bottom = header ? header.getBoundingClientRect().bottom : 0; + return Math.max(0, bottom) + 32; + }; var current = null; var queued = false; @@ -164,7 +169,7 @@ // 文書順に並んでいるので、線より下に出たらそれ以降は全部下にある var found = null; for (var i = 0; i < headings.length; i++) { - if (headings[i].getBoundingClientRect().top >= LINE) break; + if (headings[i].getBoundingClientRect().top >= line()) break; found = headings[i]; } return found;