counter-reset 是渲染时一次性初始化行为,不监听 DOM 变化;JS 新增元素需手动重置容器计数器并触发重排,否则编号停滞或显示异常。

counter-reset 为什么对 JS 新增元素没反应
它不是“监听器”,而是渲染时一次性初始化行为。DOM 插入后,浏览器不会自动回溯样式规则、重新触发 counter-increment;只要新元素没参与初始样式计算流程(比如插入后未重排或未被强制重绘),计数就卡在旧状态。
常见现象:用 el.insertAdjacentHTML("beforeend", "<div class="step">...</div>") 加新项,编号停在最后一位,新项显示 “0” 或不显示数字。
-
display: none元素完全跳过计数逻辑,但visibility: hidden仍会触发counter-increment—— 这点常被误用来“预占位”,实际无效 - 动态插入的元素若匹配了
counter-increment规则,但父容器没重置过计数器(或重置位置错误),它会尝试递增一个未初始化的计数器,结果静默失败 - React/Vue 等框架中,若组件复用 DOM 节点(如
key相同),旧节点上的计数器状态可能残留,导致新内容沿用旧值
重置作用域必须落在“新增元素的最近公共祖先”上
不能靠全局 body 或外层 wrapper 一劳永逸——尤其在 Obsidian、Notion 插件、多卡片列表等场景,多个独立编号区块共存时,counter-reset 必须绑定到每个区块自身的容器,而非它们的共同父级。
错误写法:.list-wrapper { counter-reset: step; } + 动态往里面 append() 新 .step-item → 若 .list-wrapper 之前已渲染过,新增元素不会触发重置,只按已有计数继续累加(甚至越界)。
- 正确做法:每次批量插入前,先对容器执行重置:
container.style.counterReset = "step";,再插入元素;或给容器加 class 触发重排:container.classList.add("reset-counters");配合 CSS.reset-counters { counter-reset: step; } - 更稳妥的是“全量重置 + 全量递增”:插入后调用
container.offsetHeight强制同步布局,确保所有子元素重新参与计数流 - 避免在
position: absolute或contain: layout容器内依赖计数器——这些会截断计数上下文,Safari 尤其敏感
伪元素 content 中的 counter() 显示为空?检查三个硬性条件
counter() 不是变量引用,它依赖三重链路同时成立:父容器有对应 counter-reset、当前元素(或其伪元素)有 counter-increment、content 声明非空且语法合法。
-
content: ""或content: none→ 伪元素不渲染,counter-increment也不会触发(即使写在该伪元素上) -
content: counter(step) ".";缺少引号 → 整条规则被浏览器忽略,静默失效;必须写成content: counter(step) ". "; - 把
counter-increment: step写在::after,但content写在::before→ 计数动作发生,但无处显示,视觉上就是“没编号” -
counters()第二个参数必须是字符串字面量,counters(step, var(--sep))无效,也不能是属性值如counters(step, attr(data-sep))
Obsidian / 多笔记环境里图注全显示 Fig. 1 的根因
不是 CSS 写错了,是 body 在 Obsidian 中根本不是可靠的作用域锚点——笔记内容被注入到已存在的 DOM 片段中,body 上的 counter-reset 可能被多次应用、覆盖或与其它笔记冲突。
真正起作用的是局部 wrapper:.figure-group 这类语义化容器,它把计数器生命周期锁死在单篇笔记范围内。
- 必须用
::before(双冒号),单冒号:before在 Obsidian 渲染器中可能被忽略 -
.figure-group figure要显式设counter-increment: figureCounter,不能只靠父级counter-reset - 如果图注跨多个
.figure-group段落,又想统一编号,不要试图共享计数器名,而应改用 JS 注入序号——CSS 计数器不支持跨容器联动
最麻烦的永远不是怎么写对,而是当编号错位时,得同时查三层:DOM 结构是否真被包裹、样式是否命中真实元素(而非被 display: contents 截断)、有没有隐藏节点悄悄吃掉了本该触发的 counter-increment。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











