图解CSS:奇妙的CSS计数器世界(Part1)
这是有关于列表造型设计相关的话题,简单地说,就是CSS给列表项设计样式造型。可能你会觉得这样的话题有什么好聊的,不就是用list-style 相关的属性给 li 列表项或者display为list-item的元素设置样式吗?说实话,一直以来我就是这么认为的,但自从看到 @counter-style 给列表项自定义表情符的标记时,让我感到好奇。随着继续往下探究,发现这里面有很多很有意思的东西,所以我觉得在 图解CSS系列 中单独用一篇文章和大家来聊 CSS计数器的奇妙世界。感兴趣的同学请继续往下阅读。

先从列表说起
在 HTML 中有两个列表元素,分别是 无序列表的 <ul> 和 有序列列表的 <ol>,它们都有 子元素 <li>,它被称为列表项。无序列表<ul>和有序列表<ol>最大的区别是:
- 无序列表
<ul>:代表一个项目的列表,其中列表项目的顺序并不重要,即,改变列表项目顺序不具有实质性的意义,也不会改变文档的语义 - 有序列表
<ol>:代表一个项目的列表,其中列表项目被有意地排序,这样一来,改变顺序会改变文档的语义
比如:
<div class="wrapper">
<p>I have lived in the following countries:</p>
<ul>
<li>Switzerland</li>
<li>Norway</li>
<li>United Kingdom</li>
<li>United States</li>
</ul>
</div>
<div class="wrapper">
<p>I have lived in the following countries (given in the order of when I first lived there):</p>
<ol>
<li>Switzerland</li>
<li>United Kingdom</li>
<li>United States</li>
<li>Norway</li>
</ol>
</div>
在 HTML 中除了语义化之外,<ol> 和 <ul> 相比,在<ol> 上有两 <ul> 没有的属性,即reversed和start,可以用来<ol>倒排和设置起始序列号:
<ol>
<li>Switzerland</li>
<li>United Kingdom</li>
<li>United States</li>
<li>Norway</li>
</ol>
<ol reversed>
<li>Switzerland</li>
<li>United Kingdom</li>
<li>United States</li>
<li>Norway</li>
</ol>
<ol start="5">
<li>Switzerland</li>
<li>United Kingdom</li>
<li>United States</li>
<li>Norway</li>
</ol>
<ol start="5" reversed>
<li>Switzerland</li>
<li>United Kingdom</li>
<li>United States</li>
<li>Norway</li>
</ol>
尽管我们在 HTML 中经常使用列表(<ul> 或 <ol>),但有很多 Web 开发者不会太多关注它们。其实,在 Web 中有很多东西可以很有逻辑地被标记为一个列表。比如我们在构建分步器或时间轴或排序元素可以自然地使用 <ol> 标记,但设计中的许多东西可以用无序列表<ul>来描述,比如导航菜单。
正如前面示所示,如果我们不用 CSS 对其做任何处理的话,一般情况下,客户端对列表会有一些初始样式的设置。比如,ol和ul都有一个list-style-type的属性:
ul的list-style-type的值是discol的list-style-type的值是decimal

他们的子元素 li 的 display 为会list-item。这就创建了一个块级的盒子,并有一个额外的标记框(Marker Box),标记框是用来放置标记符号(ul)或 数字(ol)的地方:

也就是说:
一个具有
display: list-item的元素为该元素的内容生成一个块盒(Block Box),并且根据list-style-type和list-style-image的值,还可能生成一个标记框(Marker Box),作为该元素是一个列表项的视觉指示。
我们使用 CSS 可以在这两个框中设置相应的CSS,也可以做很有意思的东西,从而设计各种样式网格的列表项。但这些都是后话,也是我们后面要和大家一起探讨的主要内容。
CSS 计数器的历史
在 CSS1 中就引入了列表项,并且该规范定义了十进制(deci)、小罗马(lower-roman)、大罗马(upper-roman)、小阿尔法(upper-alpha) 和 大阿尔法(upper-alpha)等计数器列表样式类型(list-style-type)。
大约在 1998 年 3 月,也就是在 CSS2 中,基本列表部分已经被移到了12生成内容、自动编号和列表(12 Generated content, automatic numbering, and lists)中。该文档定义了用户代理在渲染非来自文档的内容时的行为。
根据 CSS2.1,内容可以由 content 与 ::before 和 ::after 伪元素一起使用时生成,或者display属性值为list-item的元素生成。
大多数情况下,我们content属性的值是字符串,但你也可以使用一个 <counter> 值,它可以由 counter() 或 counters() 函数指定。其他潜在的值包括引号和属性。
自动编号由 counter-increment 和 counter-reset 两个属性控制。由这两个属性定义的计数器将与上述content属性的counter()或counters()函数一起使用。
在 2003 年 5 月份,关于生成内容的整个部分被分割出来,独立放在 CSS生成内容模块(CSS Generated Content Module Level 3),但在这之前,CSS 列表已经有了自己的工作草案。
其实在这之前,大约是 2002 年 2 月份,W3C CSS工作小组发表过第一个有关于 CSS3 列表方面的模块,不过现在被称为 CSS 列表和计数器模块(CSS Lists and Counters Module Level 3)。
另外一个相关的规范是 CSS 计数器样式模块(CSS Counter Styles Level 3),在这个模块中引入了@counter-style 规则,允许我们定义自己自定义计数器样式,用于列表项标记框(list-marker)和生成内容的计算器中。它还包括了一个预定义计数器样式的列表。比如cjk-heavenly-stem和cjk-earthly-branch。
综合所述,这些都是用于定义列表项样式造型相关的规范文档。也就是说,到目前为止,用于列表样式设置的规范分布在:
- CSS Lists and Counters Module Level 3
- CSS Generated Content Module Level 3
- CSS Counter Styles Level 3
- Ready-made Counter Styles
不同模块包含的相关属性如下图所示:
列表标记样式
HTML 中的列表项(li),不管是有序列表还是无序列表的列表项,都有默认的标记符号,无序列表一般是实心黑圆点,有序列表一般是小写的阿拉伯数字:

但很多时候默认的标记符号无法满足我们设计的需求。在 CSS 中我们可以使用 list-style 给列表项调转标记符号样式。该属性有三个子属性:
list-style: <'list-style-position'> || <'list-style-image'> || <'list-style-type'>
list-style-position:设置标记符号位置,有点类似于background-position,不过其只有outside和inside两个值,其中outside是其默认值list-style-image:设置标记符为图像,有点类似于background-image,其默认值为nonelist-style-type:设置标记符类型,其中有序列列表标记符号类型是decimal,无序列表标记符号类型是disc
先来看 list-style-type。
list-style-type
list-style-type 是用来指定列表标记类型,而且是字符串。list-style-type生效有两个条件:
- 列表项或
display: list-item的元素的::marker的content值为normal - 列表项或
display: list-item的元素没有标记图像,即list-style-image为none
满足这两个条件时,用于list-style-type的字符串会用来填充列表项的标记,相当于是 ::marker 的 content 的值。
list-style-type可以接受 <counter-style> 、<string> 或 none 值。我们平时看到的列表项标记符就是 <counter-style> 指定的列表项计数器的值。预置的 <counter-style> 其实有很多个,主要分为四大类型:
- 数字(Numeric)类型:默认用于有序列表标记符
- 字母(Alphabetic)类型:默认也用于有序列表标记符
- 符号(Symbolic)类型:默认用于无序列表标记符
- 固定(Fixed)类型:这个和中国历史有很深的联系,在中文里分为“天干地支”,其中天干是序数,和中国风水及占星术联系紧密;地支是排序,和中国十二生肖联系紧密
每一种类型都有不同的值。
| 类型 | 示例 |
|---|---|
decimal |
1, 2, 3, ..., 98, 99, 100 |
decimal-leading-zero |
01, 02, 03, ..., 98, 99, 100 |
arabic-indic |
١, ٢, ٣, ٤, ..., ٩٨, ٩٩, ١٠٠ |
armenian, upper-armenian |
Ա, Բ, Գ, ..., ՂԸ, ՂԹ, Ճ |
lower-armenian |
ա, բ, գ, ..., ղը, ղթ, ճ |
bengali |
১, ২, ৩, ..., ৯৮, ৯৯, ১০০ |
cambodian , khmer |
១, ២, ៣, ..., ៩៨, ៩៩, ១០០ |
cjk-decimal |
一, 二, 三, ..., 九八, 九九, 一〇〇 |
devanagari |
१, २, ३, ..., ९८, ९९, १०० |
georgian |
ა, ბ, გ, ..., ჟჱ, ჟთ, რ |
gujarati |
૧, ૨, ૩, ..., ૯૮, ૯૯, ૧૦૦ |
gurmukhi |
੧, ੨, ੩, ..., ੯੮, ੯੯, ੧੦੦ |
hebrew |
א, ב, ג, ..., צח, צט, ק |
kannada |
೧, ೨, ೩, ..., ೯೮, ೯೯, ೧೦೦ |
lao |
໑, ໒, ໓, ..., ໙໘, ໙໙, ໑໐໐ |
malayalam |
൧, ൨, ൩, ..., ൯൮, ൯൯, ൧൦൦ |
mongolian |
᠑, ᠒, ᠓, ..., ᠙᠘, ᠙᠙, ᠑᠐᠐ |
myanmar |
၁, ၂, ၃, ..., ၉၈, ၉၉, ၁၀၀ |
oriya |
୧, ୨, ୩, ..., ୯୮, ୯୯, ୧୦୦ |
persian |
۱, ۲, ۳, ۴, ..., ۹۸, ۹۹, ۱۰۰ |
lower-roman |
i, ii, iii, ..., xcviii, xcix, c |
upper-roman |
I, II, III, ..., XCVIII, XCIX, C |
tamil |
௧, ௨, ௩, ..., ௯௮, ௯௯, ௧௦௦ |
telugu |
౧, ౨, ౩, ..., ౯౮, ౯౯, ౧౦౦ |
thai |
๑, ๒, ๓, ..., ๙๘, ๙๙, ๑๐๐ |
tibetan |
༡, ༢, ༣, ..., ༩༨, ༩༩, ༡༠༠ |
lower-alpha , lower-latin |
a, b, c, ..., z, aa, ab |
upper-alpha , upper-latin |
A, B, C, ..., Z, AA, AB |
lower-greek |
α, β, γ, ..., ω, αα, αβ |
hiragana |
あ, い, う, ..., ん, ああ, あい |
hiragana-iroha |
い, ろ, は, ..., す, いい, いろ |
katakana |
ア, イ, ウ, ..., ン, アア, アイ |
katakana-iroha |
イ, ロ, ハ, ..., ス, イイ, イロ |
disc |
• (U+2022) |
circle |
◦ (U+25E6) |
square |
▪ (U+25AA) |
disclosure-open , disclosure-closed |
适合表示组件的一些标状态标识符,打开(▾)或关闭(▸),比如 <details> |
cjk-earthly-branch |
子, 丑, 寅, ..., 亥 |
cjk-heavenly-stem |
甲, 乙, 丙, ..., 癸 |
表格中列出的是
<counter-style>中预定义的字符串标记符,详细的可以在 CSS Counter Styles Level 3 规范中查阅,后面在介绍@counter-style时还会提到这部分内容!
具体来说,标记字符串是使用指定的 <counter-style> 生成列表项计数器值的结果,前面是 <counter-style> 的前缀(prefix),后面是 <counter-style> 的后缀(suffix)。如果指定的 <counter-style> 不存在,则假定为十进制,即 decimal:
不知道你是不是和我相似,原以为列表项标记符就只有那么十来种。
list-style-type 除了可以使用 <counter-style> 指定的字符串之外,还可以是其他的任意字符串,比如表情符号,文本字符,Unicode 字符等,比如:
li {
list-style-type: "★";
}
在示例中选择不同的值,可以看到标记符的效果:
如果你不希望列表标记有任何符号,可以把list-style-type和list-style-image同时设置为none。
到此为止,只和大家探讨了 list-style-type 最基础的使用。我们暂且搁置不聊list-style-type,后面我们会继续回来和大家聊 list-style-type。
list-style-image
list-style-image 用来指定标记图像,让list-style-image生效需要确保列表项的::marker的content为normal。它的使用方式类似于background-image,接受<image>和none值。其中<image>代表一个有效的图像,指定该元素的标记图像为<image>。否则,该元素没有标记图像,类似于list-style-image设置了none。
ul li {
list-style-image: var(--svg);
}
ol li {
list-style-image: url('./rocket.svg');
}
.list__item {
display: list-item;
list-style-image: linear-gradient(to right, #09f, #90f, #f35, #90e);
}
前面提到过了,如果list-style-image为非none和list-style-type同时出现时,比如:
li {
list-style-image: url('./rocket.svg'); /* 获胜 */
list-style-type: circle; /* 无效 */
}
li {
list-style-image: none; /* 或 url() 引入图片地址是无效的 */
list-style-type: circle; /* 有效 */
}
正如上面示例所示,当你把list-style-image的值切换图像时,list-style-type就失效了。
另外,不管是list-style-image还是list-style-type值为非none时,只要列表项标记框::marker的content为非normal,比如none或空或其他值,那么list-style-image和list-style-type都将失效。
ol li {
list-style-image: url('./rocket.svg'); /* 无效 */
}
ul li {
list-style-type: circle; /* 无效 */
}
li::marker {
content: none; /* 值为normal时,list-style-type 和 list-style-image 才有效 */
}
在上面示例中,如果::marker的content值从normal切换到none时,list-style-image和list-style-type都将无效:
list-style-position
这个属性用来设置::marker是在列表项内部还是外部。主要的值有:
inside:没有特殊效果(::marker是一个内联元素,位于列表项内容的开始)outside: 如果列表项是一个块容器(Block Container),标记框(Marker Box)就是一个块容器,被放置在主块框之外,但是列表项标记与浮动相邻的位置是未定义的。 CSS 中并没有规定标记框的精确位置,也没有规定它在绘制中的顺序,但要求它放置在框的开始一侧(inline-start),即,使用marker-side所指示的框的书写模式。标记框相对于主块框的边界是固定的,不随主块框的内容滚动。如果元素溢出不可见,UA可以隐藏标记。(标记框的大小或内容可能会影响主块框的高度或其第一行的高度,并且在某些情况下可能会导致创建一个新的行框;这种交互也没有被定义。)

注意,当list-style-position的值为outside,且列表项的容器(比如ul,ol)显式设置overflow为hidden时,那么列表项标记将无法看见:
ol,ul,.lists {
overflow: hidden;
}
li, .list__item {
list-style-position: outside;
}
切换overflow的值,效果如下:
list-style
list-style 是 list-style-position、list-style-image 和 list-style-type。如果我们不希望列表项有标记符,可以设置list-style的值为none。但并不建议这样设置,我更推荐使用 list-style: none outside none。
在list-style 使用 none 可能会产生歧义,因为list-style-image和list-style-type都可以设置值为none。或者:
/* list-style-image为 none */
list-style: none disc;
/* 等同于 */
list-style: outside none disc;
/* list-style-type为 none */
list-style: none url(bullet.png);
/* 等同于 */
list-style: outside none url(bullet.png);
/* list-style-image 和 list-style-type 都为 none */
list-style: none;
但下面这种用法是一种错误的用法:
/* 语法错语 */
list-style: none disc url(bullet.png)
这是因为list-style-position 值取none是无效值,它只有inside和outside两个值。
我们上面看到的示例,都是把list-style(或他的子属性)用在列表项 li上面,其实他们也可以直接设置在 ul 和 ol 或直接设置在 display:list-item的父容器上,比如:
ol,
ul,
.lists {
list-style: inside none disc;
}
尽管 list-style 可以直接在列表项(li 或 display: list-item的元素)上指定列表标记符样式,但需要谨慎使用,比如下面这个示例,ul嵌套在 ol class="lists" 中:
<!-- HTML -->
<ol class="lists">
<li>Friday</li>
<li>
Monday
<ul>
<li>Friday</li>
<li>Monday</li>
<li>Saturday</li>
<li>Wednesday</li>
</ul>
</li>
<li>Saturday</li>
<li>Wednesday</li>
</ol>
/* CSS */
.lists li {
list-style: lower-alpha
}
ul li {
list-style: disc
}
这是因为.lists li 选择器权重(0, 1, 1)大于 ul li 选择器权重(0, 0, 2),因此ul li中的list-style被.list li的list-style覆盖了:

可以通过改变CSS选择器或调整选择器权重达到我们想要效果:
.lists > li {
list-style: lower-alpha
}
ul > li {
list-style: disc
}
或者把 list-style 用于ul或ol上:
.lists {
list-style: lower-alpha
}
ul {
list-style: disc
}
在 CSS 中能用来控制列表标记符(list-style-type指定的字符串符号)属性并不多,只能使用 color 来改变标记符颜色,font-size 来改变其大小。
li {
color: #10f;
font-size: 2rem;
}

庆幸运的是,我们可以在列表标记框::marker上设置CSS样式,如此一来,只给标记符设置样式:
li::marker,
.list__item::marker {
color: red;
font-size: 1rem;
transition: all .2s linear;
}
li:hover::marker,
.list__item:hover::marker {
color: #09f;
font-size: 2rem;
}
鼠标悬浮到列表项,你可以看到标记符样式的变化:
即使是这样,也不并不代表所有 CSS 属性都可以用于 ::marker,具体的我们后面会详细介绍。另外,正如上面示例所示,color 用于表情符号的列表标记符就无效。而且用于::marker的CSS只能影响list-style-type属性设置的列表标记符号。
marker-side
marker-side属性到目前为止还没有得到任何浏览器支持。这里简单的介绍一下。
marker-side属性指定标记框是根据列表项自身的方向性还是列表容器的方向性来定位的。它可以接受两个值:
match-self: 标记框::marker的定位根据列表项自己的dir属性或direction属性来定位match-parent: 标记框::marker的定位根据列表项的父元素的dir属性或direction属性来定位
默认情况,元素或::marker根据列表项的方向性来定位自己。然而,如果列表项与其他几个可能具有不同方向性的列表组合在一起,比如,在 HTML 的 <ul> 中具有不同 dir 属性的多个<li>,那么marker-side不同的值,标记符位置不同:
<!-- HTML -->
<ul>
<li>Friday</li>
<li dir="rtl" lang="ar">الاثنين</li>
<li>Saturday</li>
<li dir="rtl" lang="ar">الأربعاء</li>
</ul>
/* CSS */
ul {
list-style-type: '❦';
}

从我个人的角度来理解,marker-side 的实用性并不大,在 CSS 中,即使没有该属性,我们也可以依赖 direction 来调整。具体的探讨我们放到书写模式对列表标记符位置有何影响中。
注意,
marker-side可能会随着后面的发展,也有可能会有变化,甚至被废弃!
计数器样式
CSS1 给 HTML 的 ul和ol定义了少数的计数器样式,虽然在 CSS2.2 中略有扩展,但它并没有解决世界范围内的排版需求。
不过,在 CSS Counter Styles Level 3 定义了 @counter-style 规则,它允许 CSS 以一种开放的方式来解决这个问题,允许作者定义他们自己的计数器样式 。然后,这些样式可以在 list-style-type 属性或 counter() 和 counters() 函数中使用。它还定义了一些额外的预定义的计数器样式,特别是那些常见的但用 @counter-style 表示起来很复杂的样式。
计数器样式 定义了如何将一个计数值转换成一个字符串。计数器样式由以下部分组成:
- 名称(Name) :一个名称,用于识别计数器样式
- 算法(Algorithm) :一种算法,将整数的计数器值转换为基本的字符串表示法
- 负号(Negative) :它被预置或附加到负的计数器值的表示上
- 前缀(Prefix) :用于添加到表示中
- 后缀(Suffix) : 附加到表示上
- 范围(Range) :它限制了一个计数器样式所处理的值
- 语音(Spoken) :描述如何在语音合成器中读出计数器样式
- 备用(Fallback) :当计数器值超出计数器样式的范围,或计数器样式无法渲染计数器值时,用它来渲染
当被要求对一个特定的计数器值使用一个特定的计数器样式来生成一个计数器样式时,需要遵循以下步骤:
- ①:如果计数器样式未知,则退出此算法,转而使用十进制(
decimal)样式和相同的计数器值生成一个计数器表示 - ②:如果计数器值超出了计数器样式的范围,则退出此算法,转而使用计数器样式的备用样式和相同的计数器值生成一个计数器表示
- ③:使用计数器的值和计数器样式的算法,为计数器的值生成一个初始表示。如果计数器的值是负的,并且计数器样式使用负号,则使用计数器值的绝对值生成一个初始表示
- ④:按照填充(
pad)描述符的规定,将实体符(symbols)预置到表示中 - ⑤:如果计数器的值是负数,并且计数器样式使用负号,那么按照负号描述符中的规定,用计数器样式的负号包裹表示
- ⑥:返回表示
注意 :前缀(prefix)和后缀(suffix)在这个算法中不起作用。这是故意的,前缀和后缀并不是counter()或counters()函数返回的字符串的一部分。相反,前缀和后缀是由构建::marker伪元素的content属性的值的算法添加的。这也意味着前缀和后缀总是来自于指定的计数器样式,即使实际表示的由备用样式构造的。
系统的某些值(比如symbolic、additive)和某些描述符(比如pad)可以生成与开发者提供的数字大小成线性的表示。这有可能被滥用,产生过大的表示,消耗用户的内存,甚至把浏览整趴下。用户代理必须支持至少60个 Unicode 代码点的表示,但他们可以选择使用备用样式来处理长于60个 Unicode代码点的表示。
自定义计数器样式
在 CSS 中我们可以使用 @counter-style 规则来自定义一个计数器样式,即,@counter-style 规则允许开发者定义一个自定义的计数器的样式。可以像下面这样定义一个自定义计数器样式:

@counter-style 自定义计数器样式,一般的形式是:
@counter-style <counter-style-name> {
system: <counter system>
symbols: <counter symbols>
additive-symbols: <additive-symbols>
negative: <negative symbol>
prefix: <prefix>
suffix: <suffix>
range: <range>
pad: <padding>
speak-as: <speak-as>
fallback: <counter-style-name>
}
其中<counter-style-name> 是一个<custom-ident>,可以是任意你喜欢的字符串,但要注意的是,关键词none、decimal、disc、square、circle、disclosure-open和disclosure-closed不能用于<counter-style-name>。
注意:
<custom-ident>自动排除了 CSS范围内 的关键词。当然,也有例外,比如inside就是有效的,但用inside作为<counter-style-name>会和list-style-position中的inside相冲突,所以不建议这样使用。
另外,<counter-style-name> 是区分大小写的,比如 custom-counter-style 和 customCounterStyle 是表示两个不同的自定义计数器样式名称。不过,建议大家在给自定义计数器样式命名的时候,全部使用小写字母。
每个@counter-style定义的计数器样式都由一组描述符来指定,而且每个计数器风格描述符指的值可以是隐式的,也可以是显式的。如果这些描述等你没有显式指定值,将会采用规范中定义的初始值。@counter-style中描述符主要有:
system:指一个算法,用于将计数器的整数值转化为字符串表示,其初始值为symbolicnegative:指定一个符号,当计数器表示的值为负的时候,把这个符号加在值的前面或后面,其初始值为\2D(即连字符-)prefix: 指定一个符号,加在标记符的前面。前缀在最后阶段才会被加上,所以在计数器的最终表示中,它在negative前,其初始值为空字符串" "suffix: 类似于prefix,suffix指定一个符号,加在标记符的后面,其初始值为\2E\20(即实心的圆点.)range: 指定一个自定义计数器生效的范围,如果计数器的值不再这个范围内,那么自定义的计数器样式不会生效,这个时候会使用fallback指定的值,其初始值为autopad: 在你想要给标记符最小值时使用。比如说,你想要计数器从01开始,经过02,03,04,那么这时可以使用pad。对于大于pad指定值的表示符,标记会恢复为normal,其初始值为0 " "fallback: 定义一个备用的系统,当自定义的系统不能使用或者计数器的值超过了定义的范围时使用。如果备用系统也不能表示计数器的值,那么备用系统的备用系统(如果有的话)将会启用。如果没有指定备用系统,或备用系统链不能够正确表示一个值,那么最终会降为十进制样式表示。其初始值为decimalsymbols: 定义一个符号,用于标记符。符号可以包含字符串、图片或自定义的识别码。这个符号会根据system描述符里所定义的算法来构建。其初始值为n/aadditive-symbols: 尽管symbols属性中指定的符号可以被system中定义的大部分算法所使用,但是一些system属性的值,比如additive,依赖于本描述符所描述的加性元组。每个加性元组包含一个可数的符号和一个非负证书的权重。其初始值为n/aspeak-as: 定义如何在语音识别器中读出计数器样式,比如屏幕阅读器。例如基于该描述符的值,标记符的值可以作为有序列表的数字或者字幕作为无序列表的音频提示读出。其初始值为auto
在你的CSS样式表中可以使用@counter-style同时定义多个计数器样式。如果多个@counter-style的规则被定义为同一个名称,那么放在后面的那个将生效。@counter-style 规则是原子式(Atomically)级联,如果一个规则取代了另一个同名的规则,它就完全陬代了它,而不是仅仅取代它所指的特定描述符。
注意,即使是预定义的计数器样式也可以被覆盖;UA样式表发生在任何其他样式表之前,所以预定义的样式表在级联中总是失败。
@counter-style规则也符合CSS的向前兼容的解析要求,如果浏览器无法正常解析@counter-style,那么该规则定义的计数器样式规则将被视为无效,会被浏览器忽略。这个时候将会采用预置的计数器样式。
计数器算法:system描述符
@counter-style 中的system描述符是用来指定使用哪种算法来构建计数器的值。例如 cyclic (循环算法),会让计数器样式会重复循环它们的符号,而numeric(数字计数算法)将计数器样式的符号解释为数字。 整个 system 描述符的值有cyclic、 numeric 、 alphabetic 、 symbolic 、 additive 、 [fixed <integer>?] 和 [ extends <counter-style-name> ],其默认值为 symbolic。
system的每个值都与symbols或additive-symbols描述符相关联,并且有一个相应的描述符必须具备的最小长度。如果一个@counter-style的规则不能满足这个要求,它就不能定义一个计数器样式(该规则在语法上仍然有效,但没有效果)。
接下来,我们花点时间来看看system每个值对应的算法规则是什么。
循环算法:cyclic
cyclic是一个循环算法,其提供的符号会反复循环使用,当它到达列表的末尾时,又从头开始。它可以只用一个符号,也可以使用多个符号。第一个计数器符号被用作值1的表示,第二个计数器符号(如果它存在)被用作值2表示,依此类推。
如果system是cyclic,symbols描述符号必须至少包含一个计数器符号。这个system是在所有的计数器值上定义的。

尝试着在上面的示例中,更换symbols设置的字符数,你可以看到相应的效果:

从上面的示例中,不难发现cyclic的算法:
- 如果
cyclic中的symbols只有一个计数器符号,那么所有列表项的标记符都会是symbols指定的标记符,比如上图中最左侧的效果 - 如果
cyclic中的symbols有N个计数器符号,并且列表项的总个数是V,则标记符会循环V / N值取整,余下的余数从symbols中开始计
比如上面示例中,列表项总数是 19(有19个li),当symbols的计数器符号N的值是:
当 N=2 时,V / N = 19 / 2 = 9.5,取9.5的整数值9,表示会循环 9 次,并余下 1个,从symbols最开始的计:

当 N=3 时,V / N = 19 / 3 = 6.333,取6.333的整数值6,表示会循环 6 次,并余下 1个,从symbols最开始计:

依此类推。
固定算法:fixed
固定计数器fixed在其计数器符号列表中运行一次,然后采用回退值。在列表项数量有限情况下,这个计数器样式是很有用的。例如在symbols指定了有限数量的符号(Unicode)。
如果 system 是 fixed,symbols描述符号必须至少包含一个计数器符号。这个system是在有限范围内的计数器值上定义的,从第一个符号值开始,其长度等于计数器符号列表的长度。
@counter-style games {
system: fixed;
symbols: '♠' '♡' '♢' '♣' '♤' '♥' '♦' '♧';
}
ul {
list-style-stype: games;
}
调整symbols的计数器数量,可以看到固定标记符的变化,余下的将会采用备用值:

第一个计数器符号是symbols的第一个值,随后的计数器值由随后的计数器符号表示。一旦计数器符号用完,进一步的值就不能用这种计数器样式表示,而必须用备用(fallback)的计数器样式,如果未设置fallback的值,将采用其初始值,即decimal。
当这个system被指定时,它可以选择在它后面提供一个整数,用来设置第一个符号值。如果省略它,第一个符号值是1。
@counter-style games {
system: fixed 5;
symbols: '♠' '♡' '♢' '♣' '♤' '♥' '♦' '♧';
}
正如上面示例所示,symbols的第一个值从第五个列表项开始,随后的计数器值由随后的计数器符号表示。一旦计数器符号用完,进一步的值就会采用fallback的值。另外,在指定的第一个符号值之前也会采用备用值,比如示例中的第一个到第四个列表项,采用的就是fallback的值。因为示例在system指定了第一个符号的开始值是5。
重复算法:symbolic
system指定symbolic算法时,计数器系统在其提供的符号中反复循环,这里的循环不是列表项顺序的循环,而在计数器符号自身的重复,将会以 N 的倍数重复。例如,symbols指定的符号是 * 和 †(即symbols: '*' '†'),那么:
- 第一个列表项的标记符号是
* - 第二个列表项的标记符号是
† - 第三个列表项的标记符号是
** - 第四个列表项的标记符号是
†† - 以此类推
我们来看一个具体的示例:
@counter-style chess {
system: symbolic;
symbols: "♔" "♕" "♚" "♛";
}
ul {
list-style-type: chess;
}
如果system是symbolic,那么symbols描述符必须至少包含一个计数器符号。这个系统只在严格意义上的正数计数器值上定义。
再来看一个示例,比如说,让列表项标记符号看起来类似大写字母(即upper-alpha),但在第27个列表项开始是 AA、BB、CC这样的,我们就可以使用symbolic来实现,只不过需要在symbols中指定计数器符号是A~Z字母:
@counter-style upper-alpha-legal {
system: symbolic;
symbols: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z;
}
ul {
list-style-type: upper-alpha-legal;
}
这种自定义方式和预定义的upper-alpha计数器方式,在前27个是相似的,但从第27个列表项开始就有差异了,比如:

symbolic 算法如下:
设N为计数器符号列表的长度(即symbols指定的计数器符号的数量),V最初为计数值,S最初为空字符串,symbol(n)为计数符号列表中的第n个计数符号(索引号从0开始):
- 让所选择的符号(
symbol)为symbol((V - 1) mod N) - 让表示长度为
ceil(V / N) - 将所选的符号(
symbol)追加到S中,次数等于表示长度 - 最后,返回
S
ceil()相当于 JavaScript中的Math.ceil()函数,返回大于或等于一个给定数字的最小整数;mod是取余(%)的意思,当一个操作数除以第二个操作数时,取余运算符(%)返回剩余的余数。它与被除数的符号保持一致。
双射算法:alphabetic
alphabetic将计数器符号列表解释为字母编号系统的数字,类似于默认的lower-alpha计数器样式,从a、b、c到aa、ab、ac。alphabetic系统不包含代表0的数字,因此当一个新的数字被添加时,第一个值只有第一个数字组成。alphabetic通常用于列表,也出现在许多电子表格程序中,用于对列进行编号。列表中的第一个计数器符号被解释为数字1,第二个被解释为数字2,以此类推。
如果该系统是 alphabetic,symbols必须至少包含两个计数器符号。比如下面这个示例:
@counter-style chess {
system: alphabetic;
symbols: "♔" "♕";
}
ul {
list-style-type: chess;
}
如果有N个计数器符号(symbols),表示法就是用计数器符号作为数字的N进制字母数(alphabetic)。为了构建该表示法,运行以下算法。
设N为计数器符号列表的长度,V最初为计数值,S最初为空字符串,symbol(n)为计数符号列表中的第n个计数符(索引号从0,即n从0开始计):
- 将
V设置为-1 - 将
symbol(V mod N)预置到S - 将
V设置为floor(V / N) - 最后返回
S
位值算法:numeric
numeric计数器系统将计数器符号列表解释为 place-value (位值)编号系统的数字,类似于默认十进制(decimal)计数器样式。symbols中的第一个计数符号被解释为数字0,第二个被解释为数字1,以此类推。
如果system是numeric,symbols必须至少包含两个计数器符号。这个系统是在所有的计数器值上定义的。
比如下面这个“四进制”计数器样式:
@counter-style quadratic {
system: numeric;
symbols: '0' '1' '2' '3';
}
ul {
list-style-type: numeric;
}
如果有N个计数器符号(symbols),表示法就是用计数器符号作为数字的N进制数。为了构建该表示法,运行以下算法:
设N为计数符号列表长度,V最初为计数值,S最初为空字符串,symbol(n)为计数符号列表中第n个计数符号(n从0开始索引):
- 如果
V是0,将symbol(0)追加到S中,并返回S` - 当
V 不等于0时,将symbol(V mod N)预置到S中,且将V设置为floor(V / N)` - 返回
S
累积算法: additive
additive计数器系统用于表示符号值(sign-value)的数字系统,它不是在不同的位置上重复使用数字来改变其值,而是定义具有更大价值的额外数字,因此,数字的价值可以通过将所有数字相加得到。这在罗马数字和世界各地的其他数字系统中使用。
如果system是additive,加法符号(additive-symbols)描述符必须至少包含一个加法元组。这个系统名义上是在所有的计数器值上定义的。比如:
@counter-style dice {
system: additive;
additive-symbols: 6 ⚅, 5 ⚄, 4 ⚃, 3 ⚂, 2 ⚁, 1 ⚀;
}
ul {
list-style-type: dice;
}
从现有的计数器样式中扩展:extends
extends系统允许开发者使用另一种计数器样式的算法,但改变其他方面,比如负号或后缀。如果一个计数器样式使用extends,任何未指定的描述符必须取自指定的扩展计数器样式,而不是其初始值。
如果 @counter-style 使用了 extends,它必须不包含symbols或additive-symbols描述符,否则@counter-style将无效。
如果指定的 <counter-style-name> 是 ASCII 大小写不敏感的disc、circle、square、disclosure-open或 disclosure-closed(任何预定义符号计数器样式),使用extends从规范样式表提供的规则的“标准”定义中扩展(而不是允许它们以不同的,用物质定的方式绘制的例外)。
如果指定的计数器样式名称不是任何已定义的计数器样式名称,它必须被当作是扩展十进制(decimal)计数器样式。如果一个或多个@counter-style规则与它们的扩展值形成一个循环,所有参与该循环的计数器样式必须当作是对十进制计数器样式的扩展。
@counter-style decimal-paren {
system: extends decimal;
suffix: " » ";
}
ul {
list-style-type: decimal-paren;
}
负值的格式化:negative 描述符
negative描述符定义了当计数器值为负数时如何改变表示方法。
当计数器值为负数时,值中的第一个<symbol>将被预加到表示中。如果指定了第二个<symbol>,那么当计数器值为负数时,第二个<symbol>将被附加到表示中。
例如,指定negative: "(" ")"将使负数值被包裹在圆括号中,这有时会在金融背景下使用,如(2) (1) 0 1 2 3 ...。
不是所有的system值都使用负值。特别是,如果一个计数器样式的系统(system)值是symbolic、 alphabetic 、 numeric 、 additive 或 extends 的计数器样式本身使用negative,那么它就使用负号。如果一个计数器样式不使用负号,它在生成计数器表示时会忽略负号。
标记前的符号:prefix描述符
prefix描述符指定了一个<symbol>,它被预置在标记符中,其默认值为空字符串(" ")。prefix出现在任何负号(negative)之前。

注意,
prefix仅由构建::marker伪元素默认内容的算法添加;当使用counter()或counters()函数时,prefix不会自动添加。
标记后的符号:suffix 描述符
suffix描述符指定了一个<symbol>,它被附加到标记符中,其默认值为实心圆点("\2E\20",即".")。suffix被添加到表示中的negative之后。

注意,
suffix和prefix类似,仅由构建::marker伪元素默认内容的算法添加;当使用counter()或counters()函数时,suffix不会自动添加。
限制计数器范围:range描述符
range描述符定义了计数器样式所定义的范围。如果一个计数器样式被用来表示一个超出其范围的计数器值,该计数器样式反而会下降到其回退(fallback)的计数器样式。
range可取的值有auto和[ [ <integer> | infinite ]{2} ]#,其初始值为auto。
当range取值为auto时:
- 这个范围取决于计数器系统
- 对于
cyclic、numeric和fixed算法(system),其范围是负无穷到正无穷(-∞ ~ +∞) - 对于
alphabetic和symbolic算法(system),其范围是1到正无穷大(1 ~ +∞) - 对于
additive算法(system),其范围是0到正无穷大(0 ~ +∞) - 对于
extends算法(system),范围是自动生成的扩展系统的任何内容;如果扩展一个复杂的预定义样式,范围是该样式的定义范围
当range取值[[<integer> | infinite]{2}] #时,它定义了一个用逗号(,)隔开的范围列表。对于每个单独的范围,第一个值是下限,第二个值是上限。这个范围是包容性的,它同时包含了上限和下限的数字。如果无穷值被用作一个范围的第一个值,它代表负无穷(-∞);如果被用作第二个值,它代表正无穷(+∞)。计数器样式的范围是列表中定义的所有范围的联合。如果任何范围的下限高于(大于)上限,整个描述符是无效的,必须被忽略。
零填充和恒定宽度表示:pad 描述符
pad描述符允许你指定一种“固定宽度”(Fixed-width)的计数器样式,在这种样式中,小于pad值的表示法将被填充一个特定的<symbol>。大于指定的pad值的表示法将被正常构建。
pad的值为<integer [0,∞]> &&<symbol>,它的初始值为0 ""。
其中<integer>指定了一个所有计数器表示必须达到的最小长度。让差值为所提供的<integer>减去计数器值的初始表示中的字母簇的数量。(注意,根据生成计数器表示法的算法,这发生在添加前缀(prefix)、后缀(suffix)或负数(negative)之前)。如果计数器的值是负的,并且计数器样式使用负号,那么就用计数器样式的负描述符的<symbol>。如果差值大于零,则将指定的<symbol>的差值副本预置到表示中。
注意,负的
<integer>值是不允许的。
最常见的“固定宽度”(Fixed-width)编号的例子是用0填充。例如,如果你知道所使用的数字将小于1000,可以用一个pad: 3 "0"来垫宽,确保所有的表示法都是三位宽。例如,这将导致1被表示为001,20表示为020,300被表示为300,4000被表示为4000,而-5被表示为-05。
注意,pad描述符计算表示法中的字母簇的数量,但用<symbols>进行填充。如果指定的pad的<symbol>是多字符,这很可能不会产生预期的效果。不幸的是,没有办法在不违反有用约束条件的情况下使用pad中<symbol>中的字母簇的数量。建议开发者在pad描述符中只指定单一字母簇的<symbol>。
定义回退值:fallback 描述符
fallback描述符指定了一个备用的(回退)的计数器样式,当当前的计数器样式不能为一个给定的计数器值创建表示时,将被使用。例如,如果一个以 1 ~ 10为范围定义的计数器样式被要求表示一个11的计数器值,那么该计数器值的表示就会用fallback指定的样式。
如果fallback的值不是任何定义的计数器样式的名称,fallback描述符的使用值就会被替换为decimal。同样的,在fallback寻找可以渲染给定计数器值的计数器样式时,如果检测到指定回退中的循环,必须使用十进制样式来替代。
标记字符:symbols 和 additive-symbols 描述符
symbols和additive-symbols描述符指定系统描述符所指定的标记构建算法所使用的符号。如果计数器系统(system)是cyclic、numeric、alphabetic、symbolic或fixed,@counter-style规则必须有一个有效的符号(symbols);如果计数器系统(system)是additive,则必须有一个有效的additive-symbols描述符,否则,@counter-style没有定义一个计数器样式(但仍然是一个有效的规则)。
有些计数器系统(system)指定符号描述符(symbols)必须至少有两个条目。如果计数器样式的系统是这样的,而符号描述符(symbols)只有一个条目,那么 @counter-style 规则就没有定义计数器样式。
符号(symbols)描述符的值中的每一个条目都定义了一个计数器符号,根据计数器样式的系统,它的解释是不同的。additive-symbols描述符的值中的每个条目都定义了个加法元组,它由一个计数器符号和一个整数权重组成。每个权重必须是一个非负的整数,加法无组必须按照严格的权重递减顺序来指定,否则,声明是无效的,必须被忽略。
计数器符号可以是字符串、图像和标识符,这三种类型可以混合在一个描述符中。计数器的表示方法将计数器符号串联起来构建的。标识符被渲染为包含相同字符的字符串。图像被渲染为内联替换的元素。图像计数器符号的默认对象大小是一个1em x 1em的正方形。
语音合成:speak-as
一种计数器样式可以被构建为具有明显的视觉意义,但不可能通过语音合成器或其他非视觉手段来有意义地表达,或者是可能的,但在朗读时是无意义的。speak-as 描述符描述了如何给定的计数器样式来合成计数器格式的口语形式。辅助技术在读出计数器样式时应该使用这种口语形式,并且可以使用speak-as值为通知转换为语音以外的输出。
speak-as主要的值有auto、bullets、numbers、words、spell-out 和 <counter-style-name>。
auto:如果计数器样式的system是alphabetic,这个值与拼写的效果相同;如果system是cyclic,这个值与bullets效果相同;如果system是extends,这个值的效果与自动对扩展样式的效果相同。否则,此值与数字具有相同的效果bullets: UA说一个UA定义的短语或音频提示,代莆一个无序列表被读出numbers:计数器的数字值被说成是内容语言中的数字words:像平常一样为数值生成一个计数器表示法,然后在内容语言中作为普通文本朗读出来。如果计数器的表示法包含图像,则改用处理数字(numbers)的方式处理该值spell-out:为该值生成一个正常的计数器表示,然后用内容语言逐个字母地拼出它。如果 UA 不知道如何读出这些符号(或计数器表示包含图像),它必须像处理数字(numbers)一样处理该值<counter-style-name>:反之,计数器的值将以指定的样式读出(类似于为计数器值生成表示法时的回退描述符的行为)。如果指定的样式不存在,该值将被视为auto;如果在跟随speak-as引用时检测到一个循环,对于参与该循环的计数器样式,该值被视为auto
来看一个简单示例。当所使用的符号实际上不是字母时,将发音推到另一种计数器样式的能力会有所帮助。例如,这里有一个可能的定义,即使用一些特殊的 Unicode 字符的 circulared-lower-latin 计数器样式:
@counter-style circled-lower-latin {
system: alphabetic;
speak-as: lower-latin;
symbols: ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ;
suffix: " ";
}
将其系统(system)设置为alphabetic,通常会使 UA 尝试读出字符的名称,但在这种情况下,这可能是“环形字母A”这样的东西,这不太可能有意义。相反,明确的将speak-as设置为lower-latin,可以确保它们被读出相应的拉丁文字母,正如预期的那样。
定义匿名计数器样式:symbols() 函数
symbols()函数允许在一个属性值中内陪着定义一个计数器样式,当一个样式在一个样式表中只使用一次,而定义一个完整@counter-style规则将是多余的。它没有提供 @counter-style 规则的全部功能,但提供了一个足够的子集,仍然是有用的。
symbols()使用规则如下:
symbols() = symbols(<symbols-type>?[<string>|<image>]+)
<symbols-type> = cyclic | numeric | alphabetic | symbolic | fixed
symbols()函数定义了一个没有名字的匿名计数器样式,前缀(prefix)为" "(空字符串),后缀为 " "(U+0020相当于键的 SPACE 键),范围(range)为auto,回退值(fallback)为decimal,负值(negative)为"\2D"(连字符-),填充(pad)为0 " ",以及speak-as为auto。计数器样式的算法是前面介绍的system,如果system被省略,则使用symboic,以及提供的<string>和<image>作为symbols的值来构建。如果system是fixed,第一个符号(symbol)值是1。如果系统(system)是alphabetic或numeric,必须至少有两个<string>或<image>,否则该函数无效。
例如:
ol {
list-style-type: symbols("*" "\2020" "\2021" "\A7");
}
ol {
list-style-type: symbols(cyclic "*" "\2020" "\2021" "\A7");
}
ul {
list-style-type: symbols(cyclic '○' '●');
}
注意,目前
symbols()还未得到任何浏览器支持。
预定义计数器样式和@counter-style
在 CSS 的 list-style-type 中有一些预定义样式,比如我们熟悉的 decimal、lower-alpha、disc和cjk-earthly-branch等。对于这些预定义的样式标记我们可以使用 @counter-style 规则来对他们重新定义。比如:
@counter-style decimal {
system: numeric;
symbols: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';
}
@counter-style lower-alpha {
system: alphabetic;
symbols: a b c d e f g h i j k l m n o p q r s t u v w x y z;
}
@counter-style disc {
system: cyclic;
symbols: \2022;
/* • */
suffix: " ";
}
@counter-style cjk-earthly-branch {
system: fixed;
symbols: "\5B50" "\4E11" "\5BC5" "\536F" "\8FB0" "\5DF3" "\5348" "\672A" "\7533" "\9149" "\620C" "\4EA5";
/* 子 丑 寅 卯 辰 巳 午 未 申 酉 戌 亥 */
suffix: "、";
}
在 CSS Counter Styles Level 3 的第六部分,罗列了使用 @counter-style 重新定义的所有预定义列表标记。
除此之外,还有很多“现成的”计数器样式。这些现成的计数器样式是由国际化工作组在他们的“现成的计数器样式”文件中为各种世界语言提供的计数器样式。这些计数器样式也是使用@counter-style来定义的,比如阿拉伯语(Arabic):
@counter-style arabic-indic {
system: numeric;
symbols: '\660' '\661' '\662' '\663' '\664' '\665' '\666' '\667' '\668' '\669';
/* symbols: '٠' '١' '٢' '٣' '٤' '٥' '٦' '٧' '٨' '٩'; */
}
这些现成的计数器样式和客户端预置的计数器样式不同,因为这些计数器样式并不是客户端默认计数器样式,但我们可以通过@counter-style的方式来使用,比如,你可以根据自己的需要,从Ready-made Counter Styles复制你所需要的 @counter-style:
@counter-style cjk-decimal {
system: numeric;
range: 0 infinite;
symbols: '\3007' '\4E00' '\4E8C' '\4E09' '\56DB' '\4E94' '\516D' '\4E03' '\516B' '\4E5D';
/* symbols: 〇 一 二 三 四 五 六 七 八 九; */
suffix: '\3001';
/* suffix: "、" */
}
然后可以在list-style-type、counter()、counters()中使用,比如:
ul {
list-style-type: cjk-decima
}
生成内容:content
content 是 CSS Generated Content Module Level 3 规范中的一部分。在 CSS 中,我们可以使用 content 让客户端(比如浏览器)渲染并非来自文档树的内容。比如说,在给外链的文本后面添加一个标记。
a[href^="https://"]::after{
content: "⇴";
}
使用浏览器开发者工具,可以查看到在</a>前面新增了::after一个伪元素,并且通过content添加了一个Unicode 字符,即"⇴":

除此之外,content还可以像下面这样使用:
h1::before {
content: counter(section) ": ";
}
chapter {
counter-increment: chapter;
}
chapter > title::before {
content: "Chapter " counter(chapter) "\A";
}
logo {
content: url(logo.mov), url(logo.mng), url(logo.png), none;
}
figure[alt] {
content: attr(href url), attr(alt);
}
figure:not([alt]) {
content: attr(href url), contents;
}
blockquote p::before {
content: open-quote
}
blockquote p::after {
content: no-close-quote
}
blockquote p:last-child::after {
content: close-quote
}
ol.toc a::after {
content: leader('.') target-counter(attr(href), page);
}
在 CSS 中,我们可以通过两种机制来生成内容:
content属性与伪元素::before和::after一起使用content属性与伪元素::marker一起使用,::marker来自于li元素或display为list-item的元素
也就是说,content 属性用在元素的::before,::after或::marker伪元素中插入内容。使用 content 属性插入的内容都是匿名的 可替换元素。
content可接受的值有:
content: normal | none | [ <content-replacement> | <content-list> ] [/ [ <string> | <counter> ]+ ]?
<content-list> = [ <string> | contents | <image> | <quote> | <target> | <leader()> ]+
<quote> = open-quote | close-quote | no-open-quote | no-close-quote
<target> = <target-counter()> | <target-counters()> | <target-text()>
<leader()> = leader( <leader-type> )
其中normal是content的初始值,具体每个值的含义。
normal
对于一个元素或页边框(Page Margin Box),content取值为normarl时会根据不同的伪元素计算值不一样,如果是::before或::after的content取值为normal时,它的计算值为none;如果::marker的content取值为 normal时,它的计算值为normal:
.container::before,
li::marker {
content: "⇴";
}
.container:hover::before,
.container:hover li::marker {
content: none;
}
正如上面示例所示,当鼠标悬浮到.container上时,.container::before的content为none,相当于伪元素被移除;而li::marker的content为normal,列表标记回到初始值效果:
none
不会生成内容(不会产生伪元素),类似display: none一样
<content-replacement>
<content-replacement>等于<image>:
<content-replacement> = <image>
<image> = <url> | <image()> | <image-set()> | <element()> | <paint()> | <cross-fade()> | <gradient>
即,可以使用url(),指定一个外部资源(比如图片),如果该资源或图片不能显示,它就会被忽略或显示一些占位(比如无图片标记),除了url(),还可以是image()、image-set()、element()、paint()、cross-fade() 等 CSS 函数 和 CSS 渐变 <gradient> 引入的资源:
/* <gradient> */
.container::before {
content: conic-gradient(
from 90deg,
violet,
indigo,
blue,
green,
yellow,
orange,
red,
violet
);
}
/* image-set() */
ul::after {
content: image-set(url(https://static.fedev.cn/sites/default/files/blogs/2021/2108/rocket.svg) 1x);
}
/* url() */
li::marker {
content: url(https://static.fedev.cn/sites/default/files/blogs/2021/2108/rocket.svg);
}

注意,到目前为止,
element()、paint()、cross-fade()还未得到较好的支持,如果使用 CSS Houdini的 Paint API的话,可以使用paint()函数。
<content-list>
<content-list> 等于:
<content-list> = [ <string> | contents | <image> | <counter> | <quote> | <target> | <leader()> ]+
<content-list> 值用于 content 时,可以是一个或多个匿名的内联框,它包括<image>(图像)、<string>(字符串)、<counter>(计数器的值)和 元素的文本值等。
字符串:<string>
<string>可以给 content 填充指定的文本。也就是我们常说的字符串,该字符串可以是文本,也可以是Unicode字符(包括HTML实体符和Emoji表情符号)。
:root {
--content: "";
}
li::marker,
li::after {
content: var(--content);
}
正如上面示例所示,content可以是一个或多个字符:
注意: 字符串中的留白处理与我们在HTML文档中文本留白处理是相似的。多说的空格(留白)可以折叠,甚至跨越多个字符串,例如在
content中的内容是"First " " Second";,渲染出来的结果是"First Second"(两个词之间有一个可见的空格)。上面示例中也有多字符之间空格的示例,可以仔细体验。另外,示例中我们使用了CSS自定义属性,即content: var(--content)。
引号: <quote>
长期以来,HTML 中都有<q>元素,用来给引号定界。在 CSS 中有一个quotes属性,该属性与content中的*-quote值结合起来,可以用来对这种引号进行适当的样式设计。也就是说,content中的<quote>值:
<quote> = open-quote | close-quote | no-open-quote | no-close-quote
content属性的open-quote和close-quote值时,每一次出现的open-quote或close-quote都会根据嵌套的深度被quotes值中的一个字符串替换。
open-quote指的是一对引号中的第一个,close-quote指的是一对引号中的第二个。使用哪一对引号取决于引号的嵌套深度。在当前出现的文本之前,所生成的文本中出现的open-quote的次数减去close-quote的次数。如果嵌套的深度为0,则使用第一对,如果嵌套深度为1,则使用第二对,依此类推。如果深度大于配对数,则重复使用最后一对。
有些排版可能会有多个段落,排版只是希望在第一个段落的开始之处有open-quote,在最后一个段落的结尾之处有close-quote,中间的段落不需要任何引号,这个时候,我们可以在中间的段落中使用no-close-quote和no-open-quote。比如:
blockquote p::before {
content: no-open-quote;
}
blockquote p::after {
content: no-close-quote;
}
blockquote p:first-child::before {
content: open-quote;
}
blockquote p:last-child::after {
content: close-quote;
}
注意,open-quote 和 no-open-quote一般用在::before的content中,close-quote和no-close-quote一般用在::after的content中。
如果我们根据lang属性指定quotes引号样式的话,我们在content的open-quote和close-quote使用指定的引号样式:
blockquote:lang(fr) > p {
quotes: "« " " »"
}
blockquote:lang(en) > p {
quotes: "“" "”"
}
计数器:<counter>
计数器可以指定两种不同的函数:counter() 或 counters()。
counter():有counter(name)和counter(name, style)两种形式。生成的文本是该伪元素(::before、::after或::marker)指定名称(name)的最小范围的计数;格式由style指定(如果style没指定,会采用默认样式decimal)counters():也有counters(name, string)和counters(name, string, style)两种形式。生成的文本是该伪元素(::before、::after或::marker)指定名称(name)的计数器的值,从最外层到最内层由指定的字符串(string)分隔。计数器以指定的样式(sttyle)指定(如果style没指定,会采用默认样式decimal)
有关于这部分,我们后面在counter部分会详细介绍。
attr(x) 函数
在content中使用attr(x)函数,可以将元素的x属性值当作attr()函数的参数当作生成的内容赋予给content。如果元素的x属性的值是空的(未定义),则会传一个空字符串给content。比如:

效果如下:
其他
content属性的值,除了上面提到的之外还可以是 contents、<target>和<leader()>,但这几个属性值还未得到任何浏览器的支持。但这些属性值在某些场景之下是非常有用的,比如<target>和<leader()>的结合,可以很容易生成类似书的大纲排版效果:

除此之外,<content-list>中也包含<image>,它的使用方式和<content-replacement>的相似,这里不再阐述。
/ [ <string> | <counter> ]+
content属性在最后一个<content-list> 后面的斜线(/)之后指定<string>或<counter>。它所取的作用有点类似于<img>的alt属性,给图像指定可替换文本。这样做主要是为Web可访问性(A11Y)服务的。该功能特别适用于content生成的是一些特殊字符或图像。也就是说,content属性后成的斜线(/)之后指定的是可替代文本,适用于语音,生成的内容可以被辅助技术(比如屏幕阅读器)朗读出来。比如:
/* content属性值是一张图片,需要提代类似 alt 的可替代文本 */
.new::before {
content: url(./img/star.png) / "New!";
}
如果伪元素是纯粹的装饰性元素,其功能在其他地方被覆盖,那么content可以指定空的替换文本,避免辅助技术朗读出装饰性元素。比如下面这个示例:
.expandable::before {
content: "\25BA" / ""; /* 等同于 ► */
}
上面的示例,相当于DOM中的aria-expanded="false"。就上面的示例而言,如果换成下面这样的使用方式:
.expandable::before {
content: "\25BA"; /* 等同于 ► */
}
content中没有指定任何可替换文本内容(也没有显式设置空字符串),那么content的值会被朗读成“黑色的向右的箭头”。注意,朗读的结果也会因不同的客户端有没的结果。
我们也可以使用浏览器开发者工具的可访问性选项,查看content生成的内容的对应可访问树:

你可能从上图中发现了,如果content的值是<string>的话,显式指定可替换文本,哪怕是空字符串,在可访问树中可以看到其role值为StaticText;如果content的值是<image>的话,显式指定可替换文本,在可访问树中可以看到其role值为img(和<img>非常的相似)。
最后我们来看一个关于content的示例:
上面示例中未演示
<counter>,这个放到counter部分介绍!
这里花了一部分篇幅介绍content,主要是因为我们在::before、::after和::marker的伪元素生成列表标记符和计数器样式都会用到。对 content 有了一定的了解之后,我们就可以开始继续往下阅读,进入::marker伪元素部分。
