html有序列表(ol)默认不支持课程章节所需的跨层级连续编号,因其计数器每层独立重置,无法实现“2.1.1”式递进;必须用css的counter-reset与counter-increment配合嵌套结构实现。

HTML 有序列表本身不支持“课程章节”这种语义化层级结构,必须靠嵌套 + CSS 控制编号逻辑,否则会重置计数或错乱显示。
为什么 ol 默认不能直接做课程章节编号
浏览器对 ol 的计数器是「每层独立重置」的。比如你写两个并列的 <ol type="1"></ol>,第二组一定从 1 开始,而不是接续上一章的 3 或 5。课程章节要求的是跨节连续(如「第 2 章 → 第 2.1 节 → 第 2.1.1 小节」),原生 ol 不提供这种递进式计数器绑定能力。
- 直接用
start手动算值极易出错,尤其章节增删后要全手动改 -
type属性只支持有限类型(1、A、a、I、i),无法组合成「2.1.1」格式 - 嵌套时若没严格把子
ol放在父级li内部,浏览器会当成新列表,编号立刻断掉
用 counter-reset 和 counter-increment 实现真章节编号
这是目前最可靠、可维护性最强的方式,完全脱离 type 和 start 的限制,支持任意层级和格式(如「第 3 章」「3.2 节」「3.2.1」)。
- 给外层
ol设置counter-reset: chapter,内层设counter-reset: section,再内层设counter-reset: subsection - 每个
li用counter-increment增量,并用::before拼接内容,例如:content: "第 " counter(chapter) " 章"; - 注意:必须显式闭合所有
li,且子ol必须作为li的最后一个子元素,否则计数器不继承
示例关键片段:
<ol class="chapter"> <li>HTML 基础<ol><li>标签语法</li></ol> </li> <li>CSS 入门<ol><li>选择器</li></ol> </li> </ol>
对应 CSS:
.chapter { counter-reset: chapter; }
.chapter > li { counter-increment: chapter; }
.chapter > li::before { content: "第 " counter(chapter) " 章 "; }
.chapter ol { counter-reset: section; }
.chapter ol > li { counter-increment: section; }
.chapter ol > li::before { content: counter(chapter) "." counter(section) " "; }
嵌套结构写错会导致章节编号全部崩掉
常见错误不是样式没生效,而是 HTML 结构非法,导致浏览器根本无法建立计数器上下文。
- ❌ 错误:把子
ol写在父ol外面,或和li并列 —— 浏览器当它是全新列表,编号重置 - ❌ 错误:
li没闭合,或中间插了div、p等非列表项元素 —— 计数器链断裂 - ✅ 正确:子
ol必须是某个li的**直接子节点**,且该li内容结束后立即跟ol - ✅ 推荐用 VS Code 的 Emmet 输入
li>ol>li*3,自动生成合法嵌套,避免手抖漏标签
要不要用 JavaScript 动态生成章节号
不推荐。除非你有动态加载章节、实时拖拽排序等强交互需求,否则纯 CSS 计数器更轻量、SEO 友好、无 JS 依赖。
- JS 方案需监听 DOM 变化、手动遍历、重新赋值
textContent,容易和用户选中文本、屏幕阅读器冲突 - 服务端渲染(如 Jekyll、Hugo)通常内置章节编号插件,比前端 JS 更稳
- 如果必须用 JS,别直接改
li.innerHTML,用li.setAttribute('data-number', '2.1.1')配合 CSSattr(data-number),保留语义
真正难的不是写对第一级编号,而是确保三级嵌套下每一层的 counter-reset 和 counter-increment 名称不冲突、作用域不越界——这点连不少老手都会在调试时漏掉 ol 的层级选择器限定。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











