纯CSS实现点击切换与选项卡高亮的两个方案(附踩坑)
有段时间在折腾能不能尽量少写 JS,把一些常见的交互交给 CSS 去处理。
有两个效果还算典型:一个是点导航切换内容区域,另一个是选项卡选中高亮。两个都没用一行脚本,实现思路不复杂,但第一个方案的实际表现其实跟直觉有点出入,这里一并记下来。
点击切换内容——:target 的方案
这种效果经常会想到用 JS 给元素加类名,纯 CSS 的路子主要靠 :target 伪类。
原理简单:当 URL 的 hash 指向某个元素的 id 时,这个元素就会匹配上 :target,我们就可以拿它做显示/隐藏。
HTML 结构大致是这样:
<nav>
<a href="#section1">Section 1</a>
<a href="#section2">Section 2</a>
<a href="#section3">Section 3</a>
</nav>
<div id="section1" class="content">
<h2>Section 1 Content</h2>
<p>This is the content for section 1.</p>
</div>
<div id="section2" class="content hidden">
<h2>Section 2 Content</h2>
<p>This is the content for section 2.</p>
</div>
<div id="section3" class="content hidden">
<h2>Section 3 Content</h2>
<p>This is the content for section 3.</p>
</div>
CSS 这边把需要默认隐藏的板块加上 .hidden,里面就一行 display: none。然后用 :target 把它们拉回来:
.content {
border: 1px solid #ccc;
padding: 20px;
margin-bottom: 10px;
}
.hidden {
display: none;
}
#section1:target,
#section2:target,
#section3:target {
display: block;
}
导航链接的 href 和对应板块的 id 对上,点一下 hash 变了,对应板块就显示。
这里其实容易踩坑。
上面这种写法,如果点了 Section 2,Section 2 确实会显示,但 Section 1 因为没有隐藏机制,仍然摆在那里,结果就是两个板块同时出现。严格来说这不算“切换”,只是“点哪个多显示哪个”。demo 阶段可能无所谓,但真要上生产这个行为肯定不行。:target 本身只负责匹配当前 hash 的那个元素,不会主动帮你把兄弟元素藏起来。想让之前的消失,要么把所有板块默认都隐藏,让页面初次加载时空白,要么配合兄弟选择器做互斥,但那就不是这个简单例子能覆盖的了。这个限制心里有数就好,这里主要还是演示 :target 能做什么。
选项卡选中高亮——:checked 和兄弟选择器
选项卡的场景更接近实际组件需求:选中的标签要高亮,同时只展示对应的内容面板。纯 CSS 走的是 radio 类型的 input + :checked + 后续兄弟选择器。
HTML 结构放一组 radio,共享同一个 name,这样互斥是天然的。label 负责视觉呈现,内容面板跟在它们后面:
<div class="tabs">
<input type="radio" id="tab1" name="tab-group" checked>
<label for="tab1">Tab 1</label>
<input type="radio" id="tab2" name="tab-group">
<label for="tab2">Tab 2</label>
<input type="radio" id="tab3" name="tab-group">
<label for="tab3">Tab 3</label>
<div class="tab-content" id="content1">
<p>Content for Tab 1</p>
</div>
<div class="tab-content hidden" id="content2">
<p>Content for Tab 2</p>
</div>
<div class="tab-content hidden" id="content3">
<p>Content for Tab 3</p>
</div>
</div>
所有 input 隐藏,用 label 模拟选项卡外观。
内容默认全是 display: none,哪个 radio 被选中,就通过 ~ 把对应的 content 显示出来。高亮也是同理,选中状态下让对应的 label 变色:
来此加密采用主流的ACME协议,兼容性极佳。对接Let’s Encrypt、Google、ZeroSSL三大渠道,确保证书在不同浏览器、操作系统和设备上均获得高度信任,为你的网站保驾护航。
.tabs input[type="radio"] {
display: none;
}
.tabs label {
padding: 10px 20px;
border: 1px solid #ccc;
border-bottom: none;
background-color: #f9f9f9;
cursor: pointer;
}
.tabs label:hover {
background-color: #eee;
}
.tabs .tab-content {
display: none;
padding: 20px;
border: 1px solid #ccc;
border-top: none;
}
/* 内容显示 */
#tab1:checked ~ #content1,
#tab2:checked ~ #content2,
#tab3:checked ~ #content3 {
display: block;
}
/* 高亮 */
#tab1:checked ~ label[for="tab1"],
#tab2:checked ~ label[for="tab2"],
#tab3:checked ~ label[for="tab3"] {
background-color: #4CAF50;
color: white;
}
这个方案跑起来确实做到了真正的切换,因为 radio 组只有一个能处于 :checked,切换时原来的匹配失效,内容自动回退到 display: none,高亮也跟着走。结构上要求内容面板和 label 都在同一个父级里,且内容必须排在 radio 和 label 之后,否则 ~ 选不到。
这算是一个布局上的约束,一般用 flex 或 grid 调一下顺序还能接受。
这两个方案都偏实验性质,真实项目里要是交互复杂了还是得请 JS 出山,不过偶尔碰到简单的演示或者对脚本有限制的环境,知道 CSS 能顶一顶还是挺省事的。

浙公网安备 33010602011771号