文章目录不再靠 IntersectionObserver 死盯——Chrome 用一行 scroll-target-group 让浏览器自己认「读到哪了」
一篇文章的侧边目录,你想让它随着阅读高亮当前章节、把读过的章节打个勾、没读到的置灰。这个需求你写了多少年?IntersectionObserver 建一堆观察员,scroll 事件里手动算偏移,sticky 侧栏还得防抖——一整套代码下来一百多行,稍不留神就漏了某个 section 的 id。
现在这件事被 CSS 自己接过去了。Chrome 140 起落地的 scroll-target-group 属性,配合 :target-current(还有 :target-before、:target-after)三个伪类,整个 scrollspy 只要两行核心 CSS。
先看它解决什么
过去的锚点链接是「单向」的:点击 a[href="#section-1"],浏览器把你滚到那个 section。但反过来——用户从滚动条往下滑,浏览器并不知道当前是哪个 section,更不会告诉你。所以高亮当前章节这件事,只能由你写 JS 去盯。
scroll-target-group 补上的正是这条反向通路。它的规范说明写得很直接:给一组锚点标上「滚动目标组」之后,浏览器自己跑一套算法判定「当前哪个锚点对应的目标在视口里」,然后把 :target-current 加在那个锚点身上。
两行核心写法
HTML 还是最朴素的语义结构——一个 nav,里面一串指向各章节 id 的锚点:
(nav aria-label=”目录” > ol > li > a[href=”#sec-1″] 结构)
CSS 第一行,把容器声明成滚动目标组:
.toc ol {
scroll-target-group: auto;
}
第二行,给当前章节的链接上样式:
.toc a:target-current {
color: #0066cc;
font-weight: 700;
}
就这两行。没有 IntersectionObserver,没有 scroll 监听,没有 class 切换。浏览器内部帮你跟踪滚动位置、算出当前是哪个锚点,再把伪类挂上去。
三个状态伪类,把「读到哪」画出来
真正让界面好用起来的是它一次给了三个互补的状态:
:target-current—— 当前正看在视口里的那一节:target-before—— 已经滚过去的(读过的),可以置灰加删除线,做出「已完成」的暗示:target-after—— 还没滚到的,保持正常甚至调淡,跟上面区分开
这三者不是互斥开关,而是同一套滚动位置数据的三面。举个例子,一个多步骤表单的进度条,用 :target-before 把走过的步骤打上对勾、连通竖线,用 :target-current 把当前步骤高亮,用 :target-after 把未完成的留灰——纯 CSS 就能做出原本要一整个状态机逻辑的进度跟踪。
.toc a:target-before { color: #999; text-decoration: line-through; }
.toc a:target-current { color: #0066cc; font-weight: 700; }
.toc a:target-after { color: #333; }
和 CSS Carousel API 是一套东西
看代码你会觉得眼熟::target-current 并不是新面孔,它本来就在 CSS 轮播(::scroll-marker / ::scroll-marker-group)里负责标记「当前页」。scroll-target-group 做的事情,本质是「把普通 HTML 锚点提升成 scroll marker」——原来只有伪元素能当滚动标记,现在一串你本来就写好的 a[href="#id"] 也能被浏览器当成滚动标记来跟踪。
所以它天生跟前端已有的语义结构兼容:这些链接本来就是键盘可达的、本来就被屏幕阅读器识别为链接,你不需要为了启用这个效果去改 DOM 结构或加一层包裹。而且如果你在 URL 里带 #fragment 直接跳进某节,:target-current 会自动落在对应那条链接上——过去 :target 只能给目标元素加样式,没法给「指向它的那条目录链接」加样式,这个缺口也补上了。
落地建议
- 渐进增强,不要赌它全平台可用。当前只有 Chromium 系(Chrome / Edge 140+)支持,Firefox、Safari 尚未实现。把整套效果包进特性查询里,不支持就直接不显示那个侧边目录:
.toc { display: none; }
@supports (scroll-target-group: auto) {
.toc { display: block; position: sticky; top: 2rem; }
.toc ol { scroll-target-group: auto; }
.toc a:target-current { color: #0066cc; font-weight: 700; }
}
-
顺手加一行降低跳转的「跳」感:容器上
scroll-behavior: smooth;,链接颜色上给个transition: color .5s ease;,切换章节时高亮会平滑过渡而不是硬切。 -
语义优先:目录用 nav + ol + a 的朴素写法,别为了方便样式而把锚点换成 div。这套 API 的价值恰恰建立在这些元素的原生语义和行为上。
结论
一个被写了十几年的滚动高亮交互,正在从「用 JS 监听滚动 + 手算位置 + 切 class」变成「声明元素之间的关系,让浏览器去跟踪」。scroll-target-group 加三个 :target-* 伪类,把原来一百多行的 IntersectionObserver 逻辑压成了两行 CSS。它现在还是 Chromium 独占,但作为渐进增强、尤其是在自己的博客或文档站上先跑起来,是零风险的——不支持就退回到普通锚点跳转,什么都不会坏。
评论区
登录后可评论。