javascript 自动生成目录需扫描 article 内 h2/h3 标题,为无 id 标题生成安全 id,按层级构建嵌套列表,用 scrollintoview 实现平滑滚动,并支持 localstorage 持久化折叠状态。

怎么用 JavaScript 自动提取 <h2></h2> 和 <h3></h3> 生成目录
浏览器原生不提供“自动生成文章目录”功能,必须靠 JS 扫描标题元素并拼接 DOM。核心逻辑是:选中所有 <h2></h2>、<h3></h3>(按需增减),读取 textContent,生成带锚点的链接列表。
注意不要直接用 innerHTML —— 容易引入 XSS 风险;也不建议用 document.querySelectorAll('h2, h3') 后硬编码层级缩进,因为语义结构可能嵌套混乱。
实操建议:
- 用
document.querySelectorAll('article h2, article h3')限定范围,避免匹配页眉/侧边栏标题 - 为每个标题提前设置
id:若没有,用text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')生成安全 ID,并用element.id = id补上(仅当为空时) - 生成目录项时,用
h3相对于上一个h2的位置判断是否属于子节,而不是单纯看标签名
为什么点击目录项不跳转?常见锚点失效原因
生成了链接但点击没反应,大概率是锚点目标缺失或 ID 冲突。HTML 锚点跳转依赖 href="#xxx" 与对应元素的 id="xxx" 严格一致,且该元素必须存在于当前文档流中。
常见错误现象:
- 标题有
id,但值含空格或中文(如id="第二章")→ 浏览器可能忽略或解析异常 - 多个标题生成了相同 ID(比如都叫
introduction)→ 只有第一个生效 - 目录插入在
开头,但标题在异步加载的内容里(如 Markdown 渲染后)→ JS 运行太早,没扫到元素
解决办法:确保 ID 唯一、合法、可预测;若内容动态渲染,把目录生成逻辑放在渲染完成回调里,或监听 MutationObserver。
scrollIntoView() 替代原生锚点跳转更可控
原生锚点跳转会触发整页滚动,无法控制偏移、动画或行为(比如被 sticky header 遮挡)。用 scrollIntoView() 可以绕过这些限制。
实操建议:
- 给目录链接加
preventDefault(),再调用目标元素的.scrollIntoView({ behavior: 'smooth', block: 'start', margin: 60 }) - 注意兼容性:
margin不是标准参数,需用scroll-padding-topCSS 配合(作用于目标元素的父容器) - 若要兼容旧浏览器,降级方案是手动计算
window.scrollTo({ top: el.offsetTop - 60 })
如何让目录支持折叠/展开 <h3></h3> 子项
纯 CSS 无法根据父级 <h2></h2> 状态控制后续 <h3></h3> 的显隐,必须用 JS 维护状态。关键不是“隐藏 DOM”,而是控制目录结构的展开态,并同步更新对应正文区域的可见性(或仅视觉折叠)。
简化实现思路:
- 为每个
<h2></h2>目录项添加data-toggle属性,点击时切换其下一个<ul class="toc-sub"></ul>的display状态 - 子项
<h3></h3>在目录中只出现在对应<h2></h2>下方,靠 JS 构建时就组织好嵌套关系,而非全量平铺后再分组 - 避免对正文
<h3></h3>元素本身设display:none—— 会破坏语义和 SEO;折叠应仅作用于目录 UI
复杂点在于多级嵌套和状态持久化(比如刷新后保持展开),这时候就得引入 localStorage 记录展开的 h2 ID 列表,而不是靠 class 名硬匹配。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











