必须用counter-reset定义作用域起点、counter-increment触发递增、counters()拼接层级,三者缺一不可;否则编号全乱、跳号或为0。

直接用 counter-reset 和 counter-increment 配合 counters() 就能实现多级标题自动编号,不需要 JS,也不依赖 <ol>;但必须严格匹配层级作用域,否则编号全乱。
为什么 h2/h3 编号总是从 1 开始或跳号
根本原因是计数器没被正确重置或递增。浏览器不会自动为标题层级建计数器——你得手动定义每个层级的作用域起点和累加点。
-
counter-reset: section;必须写在最外层容器上(比如<article>或<body>),不能只写在h2上,否则每个h2都重置自己,结果全是「1」 -
h2要触发一级递增:counter-increment: section; -
h3不仅要递增二级计数器,还得确保它只在当前h2下重置:所以h2上要加counter-reset: subsection;,而不是全局设 - 如果漏掉某一级的
counter-increment(比如只给h2加了,h3没加),那h3的编号就永远是 0
怎么写 CSS 才让 h2 显示 1、2、3,h3 显示 1.1、1.2、2.1
关键不是拼字符串,而是用 counters() 自动提取嵌套深度。它比手动写 counter(section)"."counter(subsection) 更可靠,避免空段(如「1..」)。
article { counter-reset: section; }h2 { counter-increment: section; }h2::before { content: counter(section) ". "; }-
h2 { counter-reset: subsection; }← 注意:这行写在h2上,不是h3上 h3 { counter-increment: subsection; }-
h3::before { content: counters(section, ".") ". "; }← 这里用counters(),不是counter()
这样,每个 h2 都会重置自己的 subsection,而 counters(section, ".") 会自动按 DOM 嵌套路径取值:在第一个 h2 下的 h3 输出「1.1」,第二个 h2 下的第一个 h3 输出「2.1」。
立即学习“前端免费学习笔记(深入)”;
为什么编辑器里编号不生效或显示 NaN
多数情况是选择器没命中真实 DOM 结构,或者计数器名拼错。
- 检查开发者工具的 computed 标签页,确认目标元素(如
h3)是否真的应用了counter-increment: subsection;—— 如果没出现,说明选择器写错了(比如编辑器把h3包在<div class="content">里,你却只写了h3::before) -
counter()中的计数器名必须完全一致:counter(secion)(少个 t)→ 不显示;counter(Section)(大小写敏感)→ 不显示 - 富文本编辑器(如 Tiptap、Slate)常把标题渲染成
<p data-level="2">而非原生h2,这时就得改用属性选择器:p[data-level="2"]::before - 伪元素内容被换行挤开?加
white-space: nowrap;和user-select: none;防干扰
动态插入标题后编号断掉怎么办
CSS 计数器是静态计算的,JS 插入新 h2 或 h3 后,浏览器不会自动补上 counter-increment——你得确保新节点自带对应样式规则,或用 class 触发已有 CSS。
- 不要靠 JS 写内联
style来模拟编号,那样维护成本高、易出错 - 推荐做法:新插入的标题元素必须有正确标签(
h2/h3)且不被 wrapper 元素隔离;若编辑器强制包裹,就用更精确的选择器,比如.editor-content h2::before - 极端情况(如 SSR + 客户端 hydration 不一致),可临时用
counter-reset在插入后强制重排,但本质仍是 CSS 渲染时机问题,无法“刷新计数器”
真正麻烦的从来不是怎么写对第一层,而是第三层嵌套时 counter-reset 放错位置,或者忘了给中间某级元素加 counter-increment —— 这种错误不会报错,只会默默输出 0 或重复编号。


















