[CSS組版]目次の見出し番号を本文の見出しの`::before`から取ってくる
本記事はCSS組版アドベントカレンダー2024 2日目の記事です。
CSS組版では、ページ決定時に決定する内容の記述は処理系に任せられる一方、事前に決定可能な箇所は記述しておく必要があります。言い換えると、目次の表示内容は本文から取ってこれるが、目次項目のリスト自体は書く必要があるということです(その目次項目を本文見出しからピックアップしてリストとして配置する事前処理が手書きだろうがJavaScriptだろうがXSLTだろうが、CSS組版の責務ではありません)。
扨、見出しは次のパーツで構成されます:
- ラベル(見出し番号など)
- ボディ(見出し項目)
例として第2章 CSS組版
という見出しがあったとき、第2章
がラベル、CSS組版
がボディとなります。
ラベルについて、見出し番号を本文に直接記述するのはCSS組版的には旨味がありません。具体的には「ラベルの部分だけ小さく表示させたい!」「ボディと別行にしたい」などの要望が後から湧いたとき、マークアップをやり直しすことになったりします。スタイルシートだけ弄って解決する、これを可能にする記述が肝要です。
<main>
<section>
<h2 id="chap2">第2章 CSS組版</h2>
<!-- `第2章`だけ表示を弄りたい -->
<!-- <h2 id="chap2"><span class="custom">第2章</span> CSS組版</h2> -->
</section>
</main>
先ず、見出し番号は自動で付与するとします。するとHTMLは次。
<main>
<section>
<h2 id="chap2">CSS組版</h2>
</section>
</main>
このときラベル内容をHTMLにそう書いてあるが如く扱えるのが::before
疑似要素です。疑似要素というだけあって、アクセシビリティ的な話を除けば、スタイルシート側に記述されながら要素のように扱えます。
:root {
counter-set: chap 0; /*章番号用カウンタの宣言。 処理系によってcounter-setに対応していなくてもcounter-resetは使えたりする */
}
main > section >h2::before {
font-size: smaller; /*見出し番号はフォントサイズを落とす*/
text-decoration: underline; /*見出し番号に下線*/
content: "第" counter(chap) "章"; /*章番号カウンタの使用*/
counter-increment: chap 1; /*カウンタを回す*/
margin-right: 1em; /*見出し番号と項目間に1emのアキ*/
}
組版すると次のように。
本題:目次の見出し番号に反映する
「目次項目はCSS組版の責務では~」のような話をしました。上の箇所だけを読んだ素直な皆さんは素直にこんな感じに書き始めるのではないでしょうか。
<header>
<section><h2>目次</h2>
<nav>
<ol>
<li><a href="#chap1">第1章 CSS</a></li>
<li><a href="#chap2">第2章 CSS組版</a></li>
<li><a href="#chap3">第3章 ページネーション</a></li>
</ol>
</nav>
</section>
</header>
しかしこれ、本文の見出しを変更したら一々記述を変更する感じですよね。JavaScriptなどを使って簡略化するにしても、「これがCSS組版です!」とお出しされたらげんなりしてしまう人もいるでしょう。
ではどうするのか。(先ずは)こうです。先程と同じようにラベル記述をHTMLから削除します。
<header>
<section><h2>目次</h2>
<nav>
<ol>
<li><a href="#chap1">CSS</a></li>
<li><a href="#chap2">CSS組版</a></li>
<li><a href="#chap3">ページネーション</a></li>
</ol>
</nav>
</section>
</header>
序でに、一般的な目次用のCSSを書いておきます。
/* ol要素で書くが、::markerは使わない */
body > header > section > nav ol {list-style:none;}
/*項目のページ番号を表示させる*/
header >section > nav a::after {
content: leader('.') target-counter(attr(href), page);
text-align-last: justify;
}
目次項目とそのページ番号間の「...」を作るのがleader()
です。
a
のhref
で示したidが対象の見出し項目なので、そのターゲットでのpage
カウンタの値を示せ、というのがtarget-counter(attr(href), page)
の指定です。
では、本文の見出しから、見出し番号を取って来ましょう。
header > section > nav a::before {
content: target-text(attr(href),before);
margin-right: 1em;
}
はい。a
のhref
をattr(href)
で引っぱって来ているのは先程と変わりません。しかし指定はtarget-text()
;ズバリ該当箇所のテキストを持って来る函数です。そして第2引数にbefore
とついています。そう、before疑似要素内のテキストも持って来れてしまうんですね! 勝った! 第3部、完!
まだもうちょっとだけ続くんじゃよ。
実はcontent
ですが、::before
・::after
ではなく要素のセレクタにも書くことが可能です。その場合、既にHTMLで記述されている内容を消し飛ばしてcontent
に置き換えます。つまり目次の最小記述はこう!
<header>
<section><h2>目次</h2>
<nav>
<ol>
<li><a href="#chap1"></a></li>
<li><a href="#chap2"></a></li>
<li><a href="#chap3"></a></li>
</ol>
</nav>
</section>
</header>
header >section > nav a {
text-decoration: none;
color: inherit;
content: target-text(attr(href));
}
空のa
なんて書いていいのか、でもCSS組版用にJSで自動生成するのがコレなら別にいいんじゃない?
終わり
で、組版してみましょう。
3日目はhidarumaさんです! 今日もギリギリなので間に合わないかもしれんね。
id
セクショニングと見出しのセクションのid
属性をsection
のようにセクショニングのコンテナが持つかh2
のように見出しの構造が持つかはちょっと悩みますが、殊CSS組版の場合はid
から近くの構造ををCSSから辿る方法が限られるため、見出し構造が持つことをオススメします。
Discussion