toc生成需限定容器提取标题、动态补id、用intersectionobserver监听滚动、scroll-margin-top处理偏移、栈式计算层级缩进、并确保nav+aria-current可访问性。

用 document.querySelectorAll 提取标题并生成 TOC 结构
浏览器原生不提供自动 TOC 生成 API,必须手动遍历 DOM 中的标题节点。关键不是“选对标签”,而是“选对层级和范围”——h1 到 h6 都要抓,但得排除非内容区(比如页眉、侧边栏)里的标题。
常见错误是直接 document.querySelectorAll('h1, h2, h3, h4, h5, h6'),结果把导航栏里伪装成 h2 的按钮也塞进目录了。应该限定容器,比如只扫 main 或带 class="content" 的元素:
const headers = document.querySelectorAll('main h1, main h2, main h3, main h4, main h5, main h6');
提取时注意:每个标题需有唯一 id,没有就动态补上,否则滚动定位会失效。补 id 推荐用标题文本转 kebab-case,避免空格或特殊字符导致 CSS 选择器出错。
滚动监听时用 IntersectionObserver 替代 scroll 事件
监听滚动来高亮当前章节,老做法是绑定 window.addEventListener('scroll', ...),但性能差、触发太密、容易卡顿。现代方案是 IntersectionObserver,它由浏览器原生优化,且能精确控制“多少比例进入视口才算激活”。
要点如下:
- 观察所有标题节点,设
threshold: [0.1]表示只要顶部 10% 进入视口就触发 - 只监听
isIntersecting === true的情况,避免反复触发 - 高亮逻辑应清空之前所有
.toc-active,再给当前标题对应 TOC 条目加 class - 不要在回调里调用
getBoundingClientRect()或其他重排重绘操作
注意兼容性:IE 不支持 IntersectionObserver,如需兼容,得 fallback 到节流后的 scroll + getBoundingClientRect,但多数项目已可忽略 IE。
scrollIntoView 定位点击跳转时的偏移与平滑问题
TOC 条目点击后跳转到对应标题,默认行为是顶部对齐,但常被固定头部遮挡。解决方法不是改 CSS 的 margin-top,而是用 scrollIntoView 的 offset 选项(实际不支持),所以得手动计算偏移:
headerElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
然后立刻调整 window.scrollY:
window.scrollTo({ top: window.scrollY - 80, behavior: 'smooth' });
更稳妥的做法是在目标元素上加伪元素或 padding,并用 scroll-margin-top:
main h2 { scroll-margin-top: 80px; }
这个 CSS 属性直接告诉浏览器“滚动对齐时,顶部留 80px 空间”,无需 JS 干预,且兼容 Chrome 89+、Firefox 90+、Safari 15.4+。老版本 Safari 会忽略,得 JS fallback。
TOC 层级缩进依赖标题顺序而非标签名
自动生成的目录如果只是按 h1/h2 硬套缩进,会出错——比如文档里先 h2 再 h1,或中间跳过 h3 直接 h4。真实层级应基于“标题在文档流中的嵌套深度”,即前一个标题的级别决定下一个是否为子项。
实现思路是维护一个栈:
- 遇到
h1,清空栈,push(1),层级 = 0 - 遇到
h2,若栈顶是 1,则 push(2),层级 = 1;若栈顶是 3,说明结构异常,pop 直到 ≤2 再 push - 每生成一个 TOC 条目,缩进用
padding-left: calc(1rem * 层级)
别用 margin-left 或 text-indent 控制缩进,它们在 RTL 页面或字体变化时容易错位。用 padding-left + calc() 最可控。
最易被忽略的是:TOC 的可访问性。每个链接必须有 aria-current="true" 标记当前项,且整个 TOC 包在 nav 元素中并配 aria-label="Table of contents"。否则屏幕阅读器用户无法感知当前位置。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











