纯 HTML + CSS + JS 实现多级目录树完全可行,需规避 ul 嵌套编号重置、li 未闭合、div 直接嵌入 ul 等陷阱,用 CSS counter 实现层级编号,JS 控制展开折叠并阻止冒泡,避免 details/summary 的多级限制。

纯 HTML + CSS + JS 实现多级目录树,不依赖框架是完全可行的,但必须绕开几个默认行为陷阱——比如 ul 嵌套不自动继承编号、li 里直接放 div 会破坏 DOM 结构、点击冒泡导致展开逻辑错乱。
用 ul/li 构建合法嵌套结构
浏览器允许无限嵌套 ul,但语义和可维护性建议控制在 3 层以内。超过这个深度,用户视觉疲劳,代码也容易失控。
-
li必须显式闭合,写成<li>节点<ul><li>子</li></ul>是合法的;但漏掉</li>会让解析器自动补全,DOM 可能错位 - 不要把
div直接塞进ul或ol:只允许li作为直接子元素,否则样式和 JS 事件绑定会失效 - 如果需要语义化容器,用
<nav id="toc-tree">或<section aria-label="目录">包裹最外层ul,提升可访问性
让层级编号显示为 1.1、2.3.1 的 CSS counter 方案
原生 ol 嵌套只会从 1 重新开始编号,不会继承父级序号。要实现真正的层级编号,必须用 CSS counter。
- 核心是
counter-reset和counters()配合:外层重置计数器,每层li自增,::before用counters(section, ".")拼接 - 注意分隔符是字符串,
counters(section, "-")得到的是1-1,不是1.1 - 若某层想跳过编号(如仅图标+文字),加
counter-increment: none即可
ol.toc {
counter-reset: section;
}
ol.toc > li {
counter-increment: section;
}
ol.toc > li::before {
content: counters(section, ".") ". ";
margin-right: 8px;
}
ol.toc ol {
counter-reset: section;
margin-left: 20px;
}
用原生 JS 控制展开/折叠,避开 jQuery 依赖
现代浏览器原生 API 已足够稳定,querySelectorAll + classList.toggle 就能搞定基础交互,无需引入 jQuery。
立即学习“前端免费学习笔记(深入)”;
- 关键点是阻止事件冒泡:
e.stopPropagation(),否则点击子li会触发父级折叠逻辑 - 只对含子
ul的li绑定事件,避免无意义监听:document.querySelectorAll('li:has(> ul)')(Chrome 105+ 支持)或退化为li.has-children类名判断 - 用
max-height+overflow: hidden替代display: none,才能配合 CSS 过渡动画;纯display切换无法过渡
为什么不用 details/summary?
HTML5 的 details 确实原生支持折叠,但它有硬伤:无法自定义箭头图标、不支持多级联动(父折叠时子不会自动收起)、summary 内容不能被 JS 安全替换(部分浏览器会重置状态)。
- 若只是单层 FAQ,
details足够轻量;但做文件树、导航目录这类多级强交互场景,它不可控 - 某些旧版 Safari 对嵌套
details渲染异常,且无法通过 CSS 修改箭头颜色或位置 - 真正省事又可控的做法:用
details做降级 fallback,再用 JS 补充完整功能 —— 但多数项目直接上 JS 更干脆
最易被忽略的是事件委托边界:当树节点动态增删时,别用 forEach 绑死初始节点,改用事件委托挂载到根 ul 上,监听 li 的 click,再用 e.target.closest('li:has(> ul)') 判断是否命中可折叠项。



















