动态目录无法仅靠实现,必须结合JavaScript遍历h2/h3标题生成锚点、用scroll-margin-top避免遮挡、IntersectionObserver精准高亮,且所有环节需保障语义与行为一致。

直接用 <ol></ol> 标签本身无法生成“动态目录”——它只是个有序列表容器,不带自动提取标题、绑定锚点、高亮当前章节等能力。真要实现可跳转、可更新、带语义的目录,必须配合 JavaScript 和语义化标题(<h2></h2>、<h3></h3>)来驱动。
为什么不能只靠 <ol></ol> + <li> 自动变目录
<ol></ol> 是纯结构标签,浏览器不会主动扫描页面内容去填充它;你手动写进去的 <li> 项也不会自动关联到对应章节,更不会响应滚动高亮。所谓“动态”,核心是「数据源(标题)→ 目录项 → 锚点跳转 → 视口同步」这一闭环,<ol></ol> 只承担最后一步的视觉呈现。
- 硬编码
<ol><li>@#@#@#@#@#@#@#@#@#@0</li></ol>是静态的,改标题就得同步改目录 - 不加 JS 的
<ol></ol>对 SEO 和可访问性无额外增益,反而可能因结构错位降低语义清晰度 - 若想支持折叠/展开、当前章节高亮、滚动定位避让固定头部,全得靠 JS 控制,和
<ol></ol>是否存在无关
document.querySelectorAll('h2, h3') 是真实起点
动态目录的第一步永远是采集数据源:遍历 DOM 中的语义化标题。用 <h2></h2> 表示一级章节、<h3></h3> 表示子节,是最通用且无障碍友好的分层方式。
- 别用
innerText提取文本——它会触发重排,改用textContent - 为空
id的标题生成唯一值:正则替换中文/空格/标点为连字符,再小写,例如"事件循环"→"shijian-xunhuan" - 生成的
<a href="#shijian-xunhuan"></a>必须指向真实存在的元素,否则跳转失败 - 把所有生成的
<li>塞进一个<ol></ol>容器里,只是为了保持序号语义和 CSS 可控性,不是功能必需
scroll-margin-top 决定点击后是否被遮挡
用户点目录跳转时,浏览器默认把目标元素顶部对齐视口顶部。如果页面有 position: fixed 的导航栏,标题就会被盖住——这不是 JS 错误,而是 CSS 滚动定位缺失。
- 给所有目标标题加统一样式:
h2, h3 { scroll-margin-top: 64px; }(数值按实际头部高度微调) - 这个属性在 Chrome/Firefox/Edge 100+ 已稳定支持;Safari 15.4+ 支持,旧版需 fallback 到
scrollIntoView({ block: 'start' })+ 手动修正window.scrollY - 千万别在 JS 里用
element.offsetTop - 64算偏移再window.scrollTo()——布局变动、字体加载、图片懒加载都会让offsetTop失准
IntersectionObserver 高亮当前章节时别踩阈值坑
用 IntersectionObserver 替代 scroll 事件监听,是为了避免主线程卡顿。但它默认 threshold: 0 极易误判:标题刚露一点边就触发,导致高亮闪烁或错位。
- 设
threshold: [0.1],表示标题顶部 10% 进入视口才认为“已到达” - 只观察
<h2></h2>,忽略<h3></h3>——子节高亮逻辑复杂,且干扰用户对主章节的感知 - 每次回调中,取第一个
isIntersecting === true的<h2></h2>,将其id同步到目录中对应<li>的class="active" - 不要在回调里直接改
style.color或style.fontWeight,统一走 class 切换,CSS 里控制样式更稳定
真正难的不是生成 <ol></ol>,而是确保每个环节都对齐语义与行为:标题有 id、链接可跳转、滚动不遮挡、高亮不抖动、SEO 可抓取。这些细节一旦漏掉一个,目录就从“辅助导航”退化成“装饰性列表”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











