§ CSS カウンターの使用
CSS カウンターでは、要素に対してカウンターを設定し、そのカウンターを表示することができます。例えば、ウェブページ内の見出し番号を自動的に振ったり、順序付きリスト
の番号を変更したり、特定のセレクターにマッチする要素のインデックス番号を表示したりするのに使用することができます。
カウンターを使うには、counter-reset
, counter-increment
, counter-set
プロパティおよび content
プロパティ値としての counter()
, counters()
関数を用います。逆行カウンターの場合は counter-reset プロパティ値としての reversed() 関数を用いることもできます。
実際に HTML と CSS の記述を見ながら、カウンターの使い方を見ていきましょう。
§基礎編:カウンターの基本的な使い方
カウンターを使うには、カウンターの初期化、カウンターの増減、カウンターの表示の 3 つの手順を組み合わせます。一つずつ見ていきましょう。
§カウンターの表示
カウンターの状態を確認するために、先に表示から試してみましょう。
§HTML
<div>
<p>(1)</p>
<p>(2)</p>
<p>(3)</p>
<p>(4)</p>
</div>
<div>
<p>(1)</p>
<p>(2)</p>
<p>(3)</p>
</div>
§CSS
p::before { content: counter(num) ". "; }
content
プロパティで counter()
関数を使っています。この記述によりカウンター名 num
のカウンターを表示できます。カウンター名には initial
, inherit
, unset
, revert
, none
以外であれば好きな名前が使えます。ここでは試しに num
としています。
この CSS を適用すると、次のように表示されます。
§Result
0. (1)
0. (2)
0. (3)
0. (4)
0. (1)
0. (2)
0. (3)
§カウンターの増減
num
カウンターを 1 ずつ増加させてみましょう。
§CSS
p { counter-increment: num 1; }
p::before { content: counter(num) ". "; }
counter-increment
プロパティでカウンター名に続けて数値を指定します。なお、数値を省略すると 1 を指定したものとして扱われます。下記の記述は、上記と同じ結果になります。
p { counter-increment: num; }
p::before { content: counter(num) ". "; }
この CSS を適用すると、次のように表示されます。
§Result
1. (1)
2. (2)
3. (3)
4. (4)
5. (1)
6. (2)
7. (3)
§カウンターの初期化
前述の CSS だけでは div
を飛び越えてカウンターが増減してしまっていました。こうしたことを防ぐため、カウンターを使いたいスコープでは必ず counter-reset
プロパティで初期化を行います。
div { counter-reset: num; }
p { counter-increment: num; }
p::before { content: counter(num) ". "; }
§Result
1. (1)
2. (2)
3. (3)
4. (4)
1. (1)
2. (2)
3. (3)
このように counter-reset
, counter-increment
, content
プロパティを組み合わせて使うのが基本的なカウンターの使い方です。counter-reset
の指定がある要素を起点に、そのカウンター名のスコープが作られます。
§counter() と counters()
次のように HTML がネストしているような場合には content
プロパティの counters()
関数が便利です。
§HTML
<ol>
<li>(1)
<ol>
<li>(1-1)</li>
<li>(1-2)</li>
</ol>
</li>
<li>(2)
<ol>
<li>(2-1)</li>
<li>(2-2)</li>
</ol>
</li>
</ol>
§CSS
ol { counter-reset: num; }
li { counter-increment: num; }
li::marker { content: counter(num) ". "; }
まずは counter()
関数を使ってみます。このような HTML と CSS は次のように表示されます。
§Result
1. (1)
1. (1-1)
2. (1-2)
2. (2)
1. (2-1)
2. (2-2)
§CSS
ol { counter-reset: num; }
li { counter-increment: num; }
li::marker { content: counters(num, "-") ". "; }
次に、counter()
の代わりに counters()
を使ってみます。この CSS を適用すると、次のように表示されます。
§Result
1. (1)
1-1. (1-1)
1-2. (1-2)
2. (2)
2-1. (2-1)
2-2. (2-2)
ネストしたHTMLに対して counter-reset
を指定することで、自動的に階層的なカウンターが作られます。階層の末尾だけを表示したい場合は counter()
関数を、階層的なカウンターをそのまま表示したい場合は counters()
関数を使うことで、期待する結果を得ることができます。
§応用編:カウンターの詳細な振る舞い
§3 ずつカウントアップする
§HTML
<div>
<p>(1)</p>
<p>(2)</p>
<p>(3)</p>
<p>(4)</p>
</div>
<div>
<p>(1)</p>
<p>(2)</p>
<p>(3)</p>
</div>
§CSS
div { counter-reset: num; }
p { counter-increment: num 3; }
p::before { content: counter(num) ". "; }
counter-increment
の数値として 3 を指定しました。この CSS を適用すると、次のように表示されます。
§Result
3. (1)
6. (2)
9. (3)
12. (4)
3. (1)
6. (2)
9. (3)
このとき、counter-set
で最初の値を変更することで、1 から始まり 3 ずつ増えるカウンターを表現できます。
§CSS
div { counter-reset: num; }
p { counter-increment: num 3; }
p:first-child { counter-set: num 1; }
p::before { content: counter(num) ". "; }
この CSS を適用すると、次のように表示されます。
§Result
1. (1)
4. (2)
7. (3)
10. (4)
1. (1)
4. (2)
7. (3)
§逆行カウンター
逆行のカウンターはカウントダウンを表現するために使います。
前述の HTML に対して、counter-reset
と counter-increment
の数値を変更してカウントダウンの表現を試みる例を見てみましょう。
§CSS
div { counter-reset: num 5; }
p { counter-increment: num -1; }
p::before { content: counter(num) ". "; }
この CSS を適用すると、次のように表示されます。
§Result
4. (1)
3. (2)
2. (3)
1. (4)
4. (1)
3. (2)
2. (3)
カウントダウンは表現できていますが、スコープごとに要素数が異なると採番の初期値がずれてしまいます。このような場合には reversed()
関数が便利です。
§CSS
div { counter-reset: reversed(num); }
p { counter-increment: num -1; }
p::before { content: counter(num) ". "; }
この CSS を適用すると、次のように表示されます。
§Result
4. (1)
3. (2)
2. (3)
1. (4)
3. (1)
2. (2)
1. (3)
カウントアップするカウンターと同様に counter-increment
の数値を指定することで、カウントダウン時の減量を変更することができます。また、counter-set
で最後の値を変更することで、1 から始まり 3 ずつ増えるカウンターを逆行するカウンターが表現できます。
§CSS
div { counter-reset: reversed(num); }
p { counter-increment: num -3; }
p:last-child { counter-set: num 1; }
p::before { content: counter(num) ". "; }
この CSS を適用すると、次のように表示されます。
§Result
10. (1)
7. (2)
4. (3)
1. (4)
7. (1)
4. (2)
1. (3)
§カウンタースタイルの変更
counter()
, counters()
関数は、それぞれ次の引数を受け付けます。
counter(<counter-name>, <counter-style>)
counters(<counter-name>, <separator>, <counter-style>)
<counter-style>
は list-style-type
と同じ値を受け付けます。
§HTML
<div>
<p>(1)</p>
<p>(2)</p>
<p>(3)</p>
<p>(4)</p>
</div>
§CSS
div { counter-reset: num; }
p { counter-increment: num; }
p::before { content: counter(num, lower-roman) ". "; }
<counter-style>
に lower-roman
を指定した例を見てみましょう。この CSS を適用すると、次のように表示されます。
§Result
i. (1)
ii. (2)
iii. (3)
iv. (4)
カウンタースタイルでは規定のものだけでなく、@counter-style
を用いて独自の表示を行うことができます。
§暗黙的な list-item カウンター
順序付きリスト
では marker に採番された値が表示されますが、この marker の値は CSS カウンターとして操作することができます。特別なカウンター名 list-item
に対して設定を行います。
内部的には、次のようなスタイルが暗黙的に指定されているように振る舞います。
ol, ul, menu { counter-reset: list-item; }
li { counter-increment: list-item; }
li::marker { content: counter(list-item) ". "; }
このため、marker の採番を 1 以外の数値でカウントアップしたり、階層的なカウンターを表示させたりすることができます。
なお、counter-reset: reversed(list-item)
が指定された場合には、暗黙的に li { counter-increment: list-item -1; }
が指定されているように振る舞います。
§HTML
<ol>
<li>(1)
<ol>
<li>(1-1)</li>
<li>(1-2)</li>
</ol>
</li>
<li>(2)
<ol>
<li>(2-1)</li>
<li>(2-2)</li>
</ol>
</li>
</ol>
§CSS
li::marker { content: counters(list-item, "-") ". "; }
この例ではネストされた順序付きリスト
の HTML に対して、li::marker
に対する content
プロパティを counter()
ではなく counters()
を用いて書き換えるだけで階層的なカウンターを表示することができます。
§Result
1. (1)
1-1. (1-1)
1-2. (1-2)
2. (2)
2-1. (2-1)
2-2. (2-2)
§フラットな HTML に対する階層的なカウンター
フラットな HTML に対して階層的な採番を行いたい場合があります。次の例を見てください。
§HTML
<h1>(1)</h1>
<h2>(1-1)</h2>
<h2>(1-2)</h2>
<h3>(1-2-1)</h3>
<h1>(2)</h1>
<h2>(2-1)</h2>
<h2>(2-2)</h2>
<h3>(2-2-1)</h3>
この HTML に対して次のように表示したいとします。
§Expected Result
1. (1)
1-1. (1-1)
1-2. (1-2)
1-2-1. (1-2-1)
2. (2)
2-1. (2-1)
2-2. (2-2)
2-2-1. (2-2-1)
これを実現させるためには、複数のカウンターを組み合わせて擬似的に階層的なカウンターを表現する必要があります。
§CSS
body { counter-reset: num1 num2 num3; }
h1 { counter-increment: num1; counter-set: num2 num3; }
h2 { counter-increment: num2; counter-set: num3; }
h3 { counter-increment: num3; }
h1::before { content: counter(num1) ". "; }
h2::before { content: counter(num1) "-" counter(num2) ". "; }
h3::before { content: counter(num1) "-" counter(num2) "-" counter(num3) ". "; }
counter-set
を用いて、例えば 2 回目の h1
が現れたら、1 回目の h1
の配下としてインクリメントされていた num2
, num3
のカウンターを 0 に戻すということを行っています。
なお、このケースでは、従来 counter-reset
が使われていましたが、counter-set
プロパティの導入に伴い counter-reset
の仕様が変更されたことで、現在では counter-set
を使わなければ期待する結果が得られないブラウザーがあります。詳細については厳密なスコープの副作用を参照してください。
ただし、counter-set
をサポートしていないブラウザーがあるかもしれません。counter-set
をサポートしていないブラウザーに対しては、従来のように counter-reset
で下位レベルの見出しのカウンターを 0 に戻す必要があります。
§実際の使用例
§セクションの明示
§HTML
<div>
<h1>Down the Rabbit-Hole</h1>
<h1>Pool of Tears</h1>
<h1>A Caucus-race and a Long Tale</h1>
</div>
§CSS
div { counter-reset: section; }
h1 { counter-increment: section; }
h1::before { content: "Section " counter(section) ": "; }
h1 { font-size: 1em; }
§Actual Result
§レンダリングされた要素のカウント
§HTML
<div>
<input id="item-1" type="checkbox" checked /><label for="item-1">item-1</label>
<input id="item-2" type="checkbox" checked /><label for="item-2">item-2</label>
<input id="item-3" type="checkbox" checked /><label for="item-3">item-3</label>
<input id="item-4" type="checkbox" checked /><label for="item-4">item-4</label>
<input id="item-5" type="checkbox" checked /><label for="item-5">item-5</label>
<table>
<thead>
<tr><th>count</th><th>index</th><th>value</th></tr>
</thead>
<tbody>
<tr class="item-1"><td></td><td>1</td><td>Down the Rabbit-Hole</td></tr>
<tr class="item-2"><td></td><td>2</td><td>Pool of Tears</td></tr>
<tr class="item-3"><td></td><td>3</td><td>A Caucus-race and a Long Tale</td></tr>
<tr class="item-4"><td></td><td>4</td><td>The Rabbit sends in a Little Bill</td></tr>
<tr class="item-5"><td></td><td>5</td><td>Advice from a Caterpillar</td></tr>
</tbody>
</table>
</div>
§CSS
/* table border */
table { margin: 20px; border-collapse: collapse; }
th, td { padding: 4px 8px; border: 1px solid #999; text-align: center; }
/* filtering by input[type="checkbox"] */
tbody tr { display: none; }
#item-1:checked ~ table .item-1 { display: table-row; }
#item-2:checked ~ table .item-2 { display: table-row; }
#item-3:checked ~ table .item-3 { display: table-row; }
#item-4:checked ~ table .item-4 { display: table-row; }
#item-5:checked ~ table .item-5 { display: table-row; }
/* CSS counter */
table { counter-reset: count; }
tbody tr { counter-increment: count; }
tbody td:nth-of-type(1)::before { content: counter(count); color: red; }
§Actual Result
§内容が空のリンクを表示する
§HTML
<p>See <a href="https://www.mozilla.org/"></a></p>
<p>If you want to know more about us, please refer to <a href="https://developer.mozilla.org/en-US/docs/MDN/About">About MDN Web Docs</a></p>
<p>See also <a href="https://developer.mozilla.org/"></a></p>
§CSS
:root { counter-reset: link; }
a[href] { counter-increment: link; }
a[href]:empty::before { content: "[" counter(link) "]"; }
§Actual Result
§list-item カウンターを逆行させる
§HTML
<ol>
<li>(1)
<ol>
<li>(1-1)</li>
<li>(1-2)</li>
</ol>
</li>
<li>(2)
<ol>
<li>(2-1)</li>
<li>(2-2)</li>
</ol>
</li>
</ol>
§CSS
ol { counter-reset: reversed(list-item); }
§Actual Result
§CSS カウンターに関する注意点
§順序付きリストの採番と CSS カウンターの関係
従来は順序付きリスト
の採番は CSS カウンターとは別で独自に実装されていましたが、counter-set
の導入に伴い CSS カウンターの仕様が改められた結果、順序付きリストの採番も CSS カウンターと同様に実装されることになりました。暗黙的な list-item カウンターを操作できるのは、この改められた仕様に追従しているブラウザーに限られます。
§不正な HTML に対する採番
§HTML
<ol>
<li>(1)</li>
<li>(2)</li>
<ol>
<li>(2-1)</li>
</ol>
<li>(3)</li>
<li>(4)</li>
</ol>
§CSS
ol { counter-reset: num; }
li { counter-increment: num; }
li::marker { content: counter(num); }
ここでは分かりやすく CSS カウンターを使って説明しますが、デフォルトの list-item の採番も同様の結果となることが CSS の仕様では想定されています。
<ol>
要素直下に <ol>
要素が出現しています。これは不正な HTML ですが、従来は上記の HTML と CSS では次のように表示されました。
§Result
1. (1)
2. (2)
3. (2-1)
2. (3)
3. (4)
不正な HTML は様々な理由で存在しており、無視のできない状況にありました(なんと document.execCommand('indent')
によって生成される DOM も不正な HTML と同じ状況を生じさせます)。この採番を妥当な HTML の場合と同じ結果にするため、一部の新しいブラウザーでは「厳密なスコープ」を実装することによって counter-reset
の振る舞いが変更されました。厳密なスコープが実装されたブラウザーでは次のように表示されます。
§Result
1. (1)
2. (2)
1. (2-1)
3. (3)
4. (4)
§厳密なスコープの副作用
§HTML
<h1>(1)</h1>
<h2>(1-1)</h2>
<h2>(1-2)</h2>
<h3>(1-2-1)</h3>
<h1>(2)</h1>
<h2>(2-1)</h2>
<h2>(2-2)</h2>
<h3>(2-2-1)</h3>
§CSS
body { counter-reset: num1 num2 num3; }
h1 { counter-increment: num1; counter-set: num2 num3; }
h2 { counter-increment: num2; counter-set: num3; }
h3 { counter-increment: num3; }
h1::before { content: counter(num1) ". "; }
h2::before { content: counter(num1) "-" counter(num2) ". "; }
h3::before { content: counter(num1) "-" counter(num2) "-" counter(num3) ". "; }
フラットな HTML に対して階層的な採番を行いたい場合は、上記のような CSS で実現することができました。
§Result
1. (1)
1-1. (1-1)
1-2. (1-2)
1-2-1. (1-2-1)
2. (2)
2-1. (2-1)
2-2. (2-2)
2-2-1. (2-2-1)
ここで、厳密なスコープを実装したブラウザーの場合、counter-set
の代わりに counter-reset
を使うと、次のように表示されてしまいます。
§Result
1. (1)
1-1. (1-1)
1-2. (1-2)
1-2-1. (1-2-1)
2. (2)
2-3. (2-1)
2-4. (2-2)
2-4-2. (2-2-1)
counter-set
が導入される前は counter-reset
を用いてこのケースが実装されていたことに注意が必要です。厳密なスコープが実装されている Firefox 82 以降ではこの問題の影響を受けます。
§暗黙的な counter-reset: list-item
の非適用性
暗黙的な list-item カウンターでは、以下の CSS が暗黙的に適用されると説明しました。
ol, ul, menu { counter-reset: list-item; }
li { counter-increment: list-item; }
li::marker { content: counter(list-item) ". "; }
/* counter-reset: reversed(list-item) が指定された場合 */
li { counter-increment: list-item -1; }
CSS の仕様では counter-increment
プロパティを(独自の CSS カウンターのための記述で)上書きした場合でも、内部的には counter-increment: list-item
が適用されているものとして振る舞うこととされています。
一方で、counter-reset
プロパティについてはそのような取り決めがありません。従来は counter-reset
が(独自の CSS カウンターのための記述で)上書きされた場合でも暗黙的に適用されているように振る舞いましたが、一部のブラウザーでは counter-reset: list-item
の暗黙的な適用がされず、li::marker
に対するデフォルトの採番が壊れてしまうことがあります。
§HTML
<ol>
<li>(1)
<ol>
<li>(1-1)</li>
<li>(1-2)</li>
</ol>
</li>
<li>(2)
<ol>
<li>(2-1)</li>
<li>(2-2)</li>
</ol>
</li>
</ol>
§CSS
ol { counter-reset: none; }
counter-reset
の list-item
に対する暗黙的な適用が行われないブラウザーの場合は、次のように表示されます。
§Result
1. (1)
2. (1-1)
3. (1-2)
4. (2)
5. (2-1)
6. (2-2)
§Expected Result
本来、期待される表示結果は次の通りです。
1. (1)
1. (1-1)
2. (1-2)
2. (2)
1. (2-1)
2. (2-2)
期待する表示結果を維持するためには、明示的に list-item
に対する counter-reset
を記述する必要があります。
§CSS
/* none の代わり */
ol { counter-reset: list-item; }
/* 独自の CSS カウンターと list-item を併用したい場合 */
ol { counter-reset: num list-item; }
暗黙的な counter-reset: list-item
の適用を行わない Firefox 68 以降ではこの問題の影響を受けます。
§関連情報
counter-reset
counter-increment
counter-set
counter()
andcounters()
functions@counter-style