counter-reset 是css中用于初始化计数器的属性,它定义计数器名称及起始值,作用于父容器以建立编号作用域;不同于ol的封闭编号,它可跨元素、支持嵌套、适用于任意标签,但需配合counter-increment和content才能生效。

counter-reset 是什么,为什么不用 ol 默认编号
浏览器原生 ol 确实自带编号,但它的计数器是封闭的、不可跨元素继承、不支持嵌套重置、也不能用在非 li 元素上。比如你想给一组 div 模拟章节标题编号,或让表格行、卡片列表、甚至混合了标题和段落的结构共享同一套编号逻辑,ol 就无能为力了。counter-reset 提供的是底层计数能力,它不绑定语义标签,只管“从几开始数”。
常见错误现象:counter-reset: section; 写在父容器上,但子元素没用 counter-increment 或 content: counter(section);,结果编号完全不显示;或者重置写在了错误层级(比如写在了要编号的元素自身上,而不是其父级),导致每次渲染都重置,编号永远是 1。
-
counter-reset必须作用于计数器的“作用域根”,通常是包含所有待编号子项的父容器 - 每个要显示编号的元素,必须显式调用
counter-increment(触发计数)和content(输出值) - 计数器名区分大小写,且不能含空格或特殊符号,推荐全小写加短横线,如
chapter、step-num
如何给任意元素添加自动编号(非 ol/li 场景)
典型使用场景:文档中混合了 h2、section、自定义组件,需要统一按“1.1”“1.2”“2.1”这样的层级编号;或者表单里多个 fieldset 需独立编号;又或者卡片列表每张卡左上角显示 “#3” 这样的序号。
关键不是“怎么写 CSS”,而是“谁负责重置、谁负责递增、谁负责显示”。三者缺一不可:
立即学习“前端免费学习笔记(深入)”;
- 父容器设
counter-reset: item;(初始化计数器) - 每个待编号子元素设
counter-increment: item;(每次渲染+1) - 用伪元素(通常是
::before)配合content: counter(item);输出数字 - 若需多级(如 2.1),父级用
counter-reset: chapter; content: counters(chapter, "."),子级再counter-increment: section;并用counters()
示例:给 class="card" 的 div 添加 #1、#2 编号
section {
counter-reset: card-count;
}
.card::before {
counter-increment: card-count;
content: "#" counter(card-count) " ";
font-weight: bold;
}
counter-reset 和 counter-increment 的参数差异与陷阱
很多人以为 counter-reset 只能写一个名字,其实它支持两个参数:counter-reset: name value;,其中 value 是起始值,默认为 0。这意味着你可以让它从 100 开始,也可以从 -5 开始——但要注意,负数不会报错,但可能造成后续编号逻辑混乱,尤其配合 counters() 嵌套时。
另一个常被忽略的点:counter-increment 支持一次递增多个计数器,也支持指定步长,比如 counter-increment: step 2; 表示每次 +2;而 counter-reset 不支持步长,只管初始值。
-
counter-reset: foo 10;→ 下一个counter(foo)是 10(不是 11) -
counter-increment: foo 3;→ 当前元素触发后,foo 增加 3 - 多个计数器同时操作:
counter-increment: level section;,会分别对两个计数器 +1 - 如果某元素既
counter-reset又counter-increment同一计数器,reset 优先级更高,相当于先重置再+1
兼容性与性能注意点
counter-reset 和相关属性在所有现代浏览器中都稳定支持(Chrome 2+、Firefox 1+、Safari 3.1+、Edge 12+),IE8+ 也支持基础功能。真正容易出问题的不是兼容性,而是性能误用和样式泄漏。
性能影响主要来自两点:一是大量嵌套的 counters() 在深层 DOM 中反复计算;二是把 counter-reset 错误地写在高频重绘元素(如滚动容器内部)上,导致每次重排都触发计数器重建。
- 避免在
body或根容器上全局counter-reset,除非你真需要全站统一编号 - 不要在动画帧或
scroll事件中动态修改counter-reset值——CSS 计数器不是 JS 变量,无法运行时更新 - 如果编号逻辑复杂(如跳过某些项、条件编号),纯 CSS 很难实现,这时候该用 JS 控制
data-*属性再用 CSS 读取,别硬扛
最常被忽略的其实是作用域隔离:同一个计数器名如果在多个不相关的组件里各自 counter-reset,它们会互相干扰。命名务必带业务前缀,比如 faq-item、doc-section,而不是泛泛的 num 或 index。










