css计数器需三步协同生效:先用counter-reset定义计数器名和初值,再用counter-increment在目标元素触发递增,最后通过content: counter()渲染。缺一不可,且作用域、伪元素和显示方式必须正确。

为什么 counter-reset 和 counter-increment 不能直接套用就生效
因为 CSS 计数器是“无状态”的:它不自动关联 DOM 结构,必须手动指定作用节点、初始化位置和递增时机。常见错误是只写 counter-increment: section; 却没在父容器设 counter-reset: section;,结果编号全为 0 或根本不出数字。
真正起作用的链条是:counter-reset 定义计数器名+初始值 → counter-increment 在目标元素上触发加一 → content: counter(...) 把值渲染出来。三者缺一不可。
-
counter-reset通常放在最外层容器(如<ol></ol>或自定义<section></section>),值默认是 0,可写成counter-reset: item 1;实现从 1 开始 -
counter-increment要加在实际要编号的元素上(如<h2></h2>、<li>),不是父容器 - 多个计数器可共存,比如
counter-reset: chapter section;,各自独立维护
怎样让嵌套标题生成「1.1」「1.2」「2.1」这类多级编号
靠的是计数器的层级继承关系:父级递增时子级计数器重置,再用 counters() 函数拼接。关键不是嵌套 HTML,而是 CSS 中对不同层级元素分别控制 counter-reset 和 counter-increment 的组合。
例如给 <h1></h1> 和 <h2></h2> 做章节目录:
h1 {
counter-reset: section;
counter-increment: chapter;
}
h2 {
counter-increment: section;
}
h1::before {
content: counter(chapter) ". ";
}
h2::before {
content: counters(chapter, ".") "." counters(section, ".") " ";
}
注意:counters() 第二个参数是分隔符,且它会把所有祖先同名计数器按顺序拼出来 —— 所以 h2 里写 counters(chapter, ".") 实际取的是最近的 chapter 值,不是自己定义的。
- 如果
h2没有counter-reset: section;,它的编号会持续累加,不会随h1变化而归零 -
counters()不支持反向嵌套(比如想让h2显示 “1-1” 但h1不显示 “1”),得换思路,比如用伪类 + 属性控制 - IE11 支持
counter()但不支持counters()的第三个参数(样式),别用counters(name, ".", decimal-leading-zero)
用 counter-increment 做非列表结构的编号(比如卡片、表格行)
只要元素能被 CSS 选中,就能编号 —— 完全不需要 <ol></ol> 或 <li>。典型场景是后台管理页的表格序号列、搜索结果卡片左上角 badge、FAQ 问答编号。
示例:给每个 <article></article> 加带前缀的序号
article {
counter-increment: article;
}
article::before {
content: "【" counter(article) "】";
font-size: 0.8em;
color: #666;
}
- 若想跳过某些
article(比如广告位),给它加counter-increment: none;,但注意这不会“跳过计数”,只是不触发递增;真要跳过得用 JS 控制 class 再配合选择器 - 动态插入元素(如 AJAX 加载新卡片)后,CSS 计数器会自动更新,无需 JS 干预 —— 这是纯 CSS 方案的最大优势
- 不要在同一个元素上反复写
counter-increment: foo; foo;,浏览器行为未定义;重复递增需用counter-increment: foo 2;
遇到编号错乱或不显示,优先检查这三处
绝大多数问题出在作用域或渲染时机上,而不是语法写错。
-
counter-reset是否写在了足够外层?比如写在<div class="list"> 上,但实际编号元素在 <code><div class="item"> 里,而 <code>.item没继承到计数器上下文 -
content是否用在了::before或::after?普通元素内联写content无效;且该伪元素必须有display(哪怕display: inline)才能渲染 - 是否误用了
counter()却期望它返回字符串?它只返回数字,要加单位或符号得拼在content字符串里,比如counter(step) ".",不能写counter(step) + "."
计数器本身不占布局空间,也不触发重排,但一旦 content 改变,伪元素会重绘 —— 所以它适合静态或低频更新场景;高频变化(如每秒刷新的计数器)建议用 JS。











