先记住这个答案
:target 伪类匹配 id 属性值等于当前 URL 片段标识符(hash)的元素。当 URL 是 example.com/page#faq 时,页面上 id="faq" 的元素匹配 :target。点击页面内的锚点链接 <a href="#faq"> 会改变 URL 的 hash,自动触发 :target 匹配。同一时刻最多只有一个元素匹配,hash 变了或清空了,原来匹配的元素就不再匹配。
- 匹配
id等于 URL hash 的元素 - 同一时刻最多一个元素匹配
- 点击锚点链接会自动触发
- 可以做纯 CSS 选项卡
最常见的用法:锚点导航高亮
长页面的目录导航会用锚点链接跳到对应章节。用户跳过去之后,:target 可以给目标章节加一个高亮样式,帮助用户确认「到了正确的位置」。
/* 目标章节加黄色背景和左边框 */
section:target {
background-color: #fffde7;
border-left: 4px solid #ffc107;
padding-left: 12px;
}
/* 平滑滚动到目标 */
html {
scroll-behavior: smooth;
}用户点击目录里的链接后,页面滚动到对应章节,同时这个章节背景变成浅黄色。再点击另一个链接时,黄色从旧章节消失,出现在新章节上。配合 scroll-behavior: smooth 还能让滚动过程有动画效果。
纯 CSS 选项卡
:target 的一个经典技巧是做不需要 JavaScript 的选项卡。每个选项卡面板是一个带 id 的元素,默认隐藏。用户点击对应的锚点链接后,URL 的 hash 变化,目标面板匹配 :target,就显示出来。
/* 所有面板默认隐藏 */
.tab-panel {
display: none;
}
/* 匹配 :target 的面板显示 */
.tab-panel:target {
display: block;
}HTML 结构是 <a href="#panel1"> 对应 <div id="panel1" class="tab-panel">。点击不同的链接就切换不同面板。这种做法的好处是完全不需要 JavaScript,而且 URL 里带着 hash,刷新页面也能恢复到正确的选项卡。
不过也有局限:页面刚加载时如果 URL 没有 hash,所有面板都不显示。通常的解决办法是让第一个面板默认显示,用 :target ~ .default-panel 这类选择器来控制。
容易答错的地方
- 以为 pushState 能触发 target
- JavaScript 的
history.pushState和history.replaceState可以改变 URL,但不会触发:target的重新计算。只有真正改变 hash(通过点击锚点链接或设置location.hash)才会让:target更新。 - 以为多个同 id 的元素都会匹配
id在同一页面内应该是唯一的。如果有多个元素写了相同的id,:target只匹配 DOM 中第一个出现的那个,其他的不会生效。而且重复id本身就是无效的 HTML。
面试官还会怎么问?
target 和 scroll-margin-top 有什么关系?
页面有固定头部(position: fixed)时,锚点跳转会让目标内容被头部遮住。给匹配 :target 的元素加 scroll-margin-top 可以解决这个问题。比如 section:target { scroll-margin-top: 80px; } 会让浏览器多滚动 80px,留出头部的空间。
怎么在 URL 没有 hash 时也让 target 有默认值?
CSS 本身做不到。:target 只在有 hash 且匹配到元素时才生效。一个变通方法是在页面加载时用 JavaScript 设置一个默认的 hash。或者在 CSS 里不依赖 :target 做默认显示,只用 :target 做覆盖。
SPA 里 target 还能用吗?
单页应用通常用路由管理 URL,hash 模式的路由(#/about)中 hash 是路由路径,不是元素 id,:target 不会匹配。如果用 history 模式路由,hash 部分可以单独用于页面内锚点,:target 就能正常工作。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。