纯css无法实现滚动时目录项自动高亮,因position:sticky仅控制自身定位且不感知滚动;高亮必须由javascript驱动,intersectionobserver是唯一合理、高性能、可落地的方案。

纯 CSS 无法实现滚动时自动高亮侧边目录项——必须用 JavaScript 配合 IntersectionObserver,position: sticky 只管“卡住”,不管“高亮”。
为什么 position: sticky 不会触发高亮
position: sticky 的行为完全静态:它只在元素进入/离开滚动容器边界时切换定位模式(relative ↔ fixed),不产生任何事件、不暴露滚动位置、也不提供当前是否“在视口内”的状态。你给目录链接加了 sticky,它只是自己停在侧边了,对右侧 h2 标题滚到哪一节毫无感知。
常见误操作包括:
- 给
.sidebar a加position: sticky,以为能联动内容滚动 - 依赖
:target伪类——它只响应 URL hash 变更(比如点击锚点),自然滚动不会触发 - 把
scroll-margin-top当成高亮判断依据——它只影响scrollIntoView()的偏移,不参与 Intersection 判断
IntersectionObserver 是唯一合理方案
它监听目标元素(如右侧的 h2)与视口或父容器的交叉状态,性能好、无重排、支持阈值控制,且现代浏览器全覆盖(Chrome 51+ / Firefox 55+ / Safari 12.1+)。
关键实操点:
- 观察目标是内容区的标题(
h2、h3等),不是侧边栏链接本身 - 设置
rootMargin: "0px 0px -50% 0px"让“一半进入视口”即视为激活,避免滚动临界点闪烁 - 每个
h2的id必须与侧边栏中对应a[href="#xxx"]的href值严格一致 - 监听回调里用
entries.forEach(entry => { if (entry.isIntersecting) { ... } })更新高亮类名,别直接操作style
侧边栏本身怎么保持固定又不干扰高亮逻辑
侧边栏用 position: sticky 是最轻量的选择,但必须满足前提条件:
- 父容器(比如
.layout)要有明确高度,推荐height: 100vh或max-height: 100vh; overflow-y: auto - 必须显式设置
top(如top: 64px),避开顶部导航栏,设为0容易遮挡 - 确保父级没加
transform、filter或perspective——这些会创建新层叠上下文,让sticky失效 - 如果侧边栏内容超长,自身要加
overflow-y: auto,而不是靠外部容器滚动
容易被忽略的兼容性与抖动细节
Chrome 120+ 在某些 transform 父级下仍有 sticky 行为 bug;Safari 对 IntersectionObserver 的 rootMargin 解析偶尔不准。真正麻烦的是滚动抖动——尤其当高亮逻辑触发重排时。
两个实际可用的缓解点:
- 给侧边栏加
will-change: transform(仅当它确实有交互动画时启用,别滥用) - 高亮更新尽量只改
className,避免频繁读取offsetTop或写style.color - 老浏览器(IE / Safari scroll 事件 +
getBoundingClientRect(),但性能差、易卡顿,非必要不写
真正难的不是写代码,而是让 IntersectionObserver 的阈值、scroll-margin-top 和侧边栏 top 值三者对齐——差几个像素,高亮就会跳变或滞后。调的时候建议先关掉所有动画,用 console.log 打印 entry.boundingClientRect 看真实位置。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











