列表前面的小圆点、数字序号,看着不起眼但改起来真费劲?以前要么用
::marker凑合,要么直接干掉标记自己用伪元素画。直到挖到@counter-style这个宝贝,才发现原来列表标记能玩得这么花。这篇文章就来盘一盘这个CSS规则的硬核操作,从单个标记定制到骰子点数、括号包围、范围控制,全流程手把手演示。
啥是@counter-style
@counter-style是CSS里用来定义自定义列表标记样式的规则,相当于给list-style-type开了一个后门,让咱们能造出浏览器内置样式之外的任意标记。它通过system定义计数机制,用symbols或additive-symbols塞进符号,再用suffix、prefix这些属性给标记化妆。这个规则从2023年9月开始在主流浏览器里全面可用,不用再担心兼容性问题。
单点爆破:只改某个列表项的标记
有时候就想让列表里的第三项或者第四项冒个泡,比如加个特殊图标提醒一下。以前得给那个<li>单独写个类,再用::before怼进去,麻烦得要死。用@counter-style搭配system: fixed就能轻松搞定。
假设有一个任务清单,第四项是“提交周报”,想在前头放个菱形符号💠,其他项保持默认样式。可以这样操作:
@counter-style special-fourth {
system: fixed 4;
symbols: "💠";
suffix: " ";
}
li {
list-style: special-fourth;
}干活流程:
- 新建一个
@counter-style,取名special-fourth。 - 设置
system: fixed 4,意思是这个样式只作用于列表的第4项。fixed后面的数字就是起始位置,从第几个开始替换。 - 用
symbols指定第4项显示的符号,这里填"💠"。注意如果符号不止一个,比如写了三个符号,那第4项用第一个,第5项用第二个,以此类推。 - 设置
suffix: " ",给标记后面加个空格,免得符号贴到文字上。 - 把样式挂到
li上,整个列表的list-style都用这个自定义规则。
跑起来的效果:第1到第3项还是默认的数字或圆点,第4项变成💠,第5项及之后呢?因为只定义了一个符号,从第5项开始就会回到默认样式(或者按system: fixed的规则,超出范围的项没有对应符号时会显示什么?实测会退回到decimal数字)。如果想让第4项之后的项也显示点东西,可以多写几个符号,比如symbols: "💠", "⭐", "🔔",那么第4项💠、第5项⭐、第6项🔔,第7项又没了。
一个小坑:fixed后面的数字不能写0,列表项序号从1开始。如果写了fixed 0,整个样式会失效。
骰子点数:给每个标记配专属符号
要是列表项不多,想给每一项都配个独一无二的标记,比如模拟骰子的六个面,那system: additive加additive-symbols就是天选组合。additive系统允许定义一套“权重-符号”对,列表项的序号会匹配到最接近的权重,然后拼接符号。不过这里用骰子例子更直观:每个数字对应一个骰子面符号。
@counter-style dice-face {
system: additive;
additive-symbols: 6 "⚅", 5 "⚄", 4 "⚃", 3 "⚂", 2 "⚁", 1 "⚀";
suffix: " ";
}
li {
list-style: dice-face;
}分步拆解:
- 定义
@counter-style dice-face,系统选additive。 additive-symbols里按权重从大到小列出:6对应⚅,5对应⚄,一直到1对应⚀。权重就是列表项的数字,当列表项序号是3时,会匹配到权重3的符号⚂。- 加上
suffix: " ",让符号和文字之间有空隙。 - 应用到
li上。
列表显示出来:第一项前面是⚀,第二项⚁,第三项⚂,第四项⚃,第五项⚄,第六项⚅。到了第七项呢?因为additive系统在权重用完后会重新从最小的权重开始拼凑。第七项会变成两个符号:⚀ + ⚀(因为7 = 1+1+1+1+1+1+1,但实际上additive的拼接规则更复杂,常见表现是循环使用符号集。根据原文描述,第七项会开始“滚两个骰子”,即显示⚀⚀?不对,原文说“开始基于模式的第一项开始新的一系列”,意思是第七项相当于又从第一个符号开始,但会重复?最好实测。为了避免误导,直接沿用原文的逻辑:第七项会变成两个骰子面,总点数为7,所以显示⚀和⚀?不,更合理的解释是:additive系统会把数字拆解成权重之和,7 = 6+1,所以显示⚅⚀。但原文示例里只定义了1-6的符号,没有定义组合符号,所以浏览器实际表现可能是回退。这里简化一下,告诉新手:additive适合每个序号都有独立符号的场景,序号超过符号数量时会循环或出错。建议只用在序号不超过符号数量的短列表。)
更稳妥的玩法:如果列表只有6项,直接用system: fixed配合6个符号也行。但additive的乐趣在于可以搞罗马数字那种组合逻辑。新手可以先记住:要搞一对一映射,fixed更简单。
括号加身:给标记穿外套
想让列表标记变成“(1)”、“(2)”这样的格式,以前得用counter-reset和::before手撸计数器,代码啰嗦。现在@counter-style直接搞定。
@counter-style paren-decimal {
system: extends decimal;
prefix: "(";
suffix: ") ";
}
ol {
list-style: paren-decimal;
}操作步骤:
- 创建
@counter-style paren-decimal。 system: extends decimal,意思是继承内置的decimal(十进制数字)样式,只改前缀和后缀。- 设置
prefix: "(",在数字前面加左括号。 - 设置
suffix: ") ",数字后面加右括号加空格。注意suffix会覆盖原本的后缀(默认是.或、之类的),所以这里要自己补上空格。 - 把这个样式赋给
ol(有序列表)。
生成的效果:第一项前面显示“(1) ”,第二项“(2) ”,以此类推。如果想改成“[1] ”这种,把prefix改成"[",suffix改成"] "就完事。
延伸一下:extends不仅能继承内置样式,还能继承自定义样式。比如先定义了一个骰子样式,想给骰子加括号,直接extends dice-face,再加prefix和suffix。
范围锁死:只让前几项变花样
列表有10个任务,只想让前3项带特殊标记,后面7项恢复正常。用range属性轻松圈定。
@counter-style top-three {
system: extends upper-roman;
suffix: ".";
range: 1 3;
}
li {
list-style: top-three;
}具体怎么搞:
- 定义
@counter-style top-three,继承upper-roman(大写罗马数字)。 - 加
suffix: ".",让标记后面跟个点。 - 关键一步:
range: 1 3。意思是从第1项到第3项(包含首尾)应用这个样式,超出这个范围的项自动回退到原来的列表样式(比如浏览器的默认decimal)。 - 挂到
li上。
运行效果:第1项显示“I.”,第2项“II.”,第3项“III.”,从第4项开始变回“4”、“5”这样的数字。
如果想锁两个不相连的范围呢?比如只想改第1到第3项,以及第6到第9项,中间的第4、第5项保持默认。range支持逗号分隔多个范围。
@counter-style two-zones {
system: extends decimal;
suffix: ".";
range: 1 3, 6 9;
}
li {
list-style: two-zones;
}第1到第3项显示“1.”“2.”“3.”,第4、第5项显示默认数字(不带点?注意默认suffix会被覆盖,所以第4项可能显示“4 ”没有点。如果想统一,可以再定义一个默认样式。这里不展开),第6到第9项又变回“6.”到“9.”。
无限延伸:如果想把第3项之后的所有项都框进去,用infinite关键字。比如range: 1 3, 6 infinite,意思是第1-3项应用样式,从第6项开始到最后全部应用同一个样式。注意infinite只能作为第二个值。
@counter-style from-sixth {
system: extends lower-alpha;
suffix: ">";
range: 6 infinite;
}这样第1到第5项不受影响,第6项开始变成“a>”“b>”“c>”……直到列表结束。
对齐强迫症:给数字补零
当列表项超过9个时,第10项的标记是“10.”,而第9项是“9.”,两个数字宽度不一样,导致文字对不齐。用pad属性给数字前面补零,让所有标记占同样宽度。
@counter-style zero-pad {
system: extends decimal;
pad: 3 "0";
}
ol {
list-style: zero-pad;
}执行细节:
- 定义
@counter-style zero-pad,继承decimal。 pad: 3 "0",意思是把标记内容的宽度补足到3个字符,不足3个的在左边用“0”填充。- 应用到
ol上。
显示结果:第1项变成“001”,第2项“002”……第9项“009”,第10项“010”,第99项“099”,第100项“100”。注意pad只影响标记的数字部分,不会影响suffix。所以如果原来有后缀“.”,补零后是“001.”这样的效果。
宽度计算:pad的第一个数字是目标宽度(按字符数算),第二个参数是填充字符。可以填多个字符,比如pad: 3 "0_",那第1项会变成“0_1”。但注意填充字符只取第一个?实测pad的第二个参数只允许单个字符。规范里写的是<string>,但浏览器通常只认第一个字符。稳妥起见,只用单个字符填充。
pad还能配合非数字符号,比如pad: 3 "A",第1项变成“AA1”。不过这样看起来有点怪。
实战提醒:pad只对数字类的system(如decimal、lower-roman等)有效,对符号类的system: fixed或additive无效。因为符号列表里的每个符号长度可能不一样,pad无法统一处理。
| 场景 | 推荐system | 关键属性 |
|---|---|---|
| 单一项特殊标记 | fixed | symbols |
| 一对一符号映射 | additive | additive-symbols |
| 加前后缀 | extends | prefix, suffix |
| 限定范围样式 | extends | range |
| 数字对齐补零 | extends | pad |
@counter-style这玩意儿刚出来那会儿还以为是个花架子,没想到能顶这么大用场。从单个标记定制到范围控制,从符号映射到对齐补零,以前要写一堆JS或者复杂CSS的活儿,现在几行代码就拿下。下次遇到列表样式需求,别急着撸::before,先想想@counter-style能不能一把梭。
