CSS :has() 选择器:父选择器终于来了
CSS :has() 选择器在 2023 年底拿到主流浏览器支持,到 2025 年已经可以放心使用了。它被称作"CSS 的父选择器"——虽然不只是选父元素,但这是它最常见的用法。
基本语法
:has() 的用法是根据后代元素的状态来匹配祖先元素:
/* 选中"包含 img 子元素的 figure" */
figure:has(img) {
border: 1px solid #ddd;
padding: 1rem;
}
/* 选中"包含 checked 的 checkbox 的 label" */
label:has(input:checked) {
font-weight: bold;
color: green;
}
/* 选中"包含 placeholder 的输入框的父容器" */
.form-group:has(input:placeholder-shown) {
/* 输入框还空着时的样式 */
}
实际使用场景
1. 表单验证
以前做表单验证状态样式需要 JavaScript 添加 class。现在纯 CSS 就能搞定:
/* 输入框获得焦点且有值时的样式 */
.form-item:has(input:focus:not(:placeholder-shown)) {
border-color: #10b981;
}
/* 输入框有值且失焦时的样式 */
.form-item:has(input:not(:placeholder-shown):not(:focus)) {
border-color: #10b981;
}
/* 输入框为空且失焦时的样式(需要必填) */
.form-item:has(input:required:placeholder-shown:not(:focus)) {
border-color: #ef4444;
}
placeholder-shown 是关键——输入框有值时 placeholder 不显示,没有值时显示。用这个伪类来判断输入框是否为空,不需要 JS。
2. 响应式组件
根据子元素的数量调整布局:
/* 子元素 <= 3 个时水平排列 */
.card-list:has(> *:nth-child(-n+3):last-child) {
display: flex;
gap: 1rem;
}
/* 子元素 > 3 个时网格布局 */
.card-list:has(> *:nth-child(4)) {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
3. 暗色模式头像适配
根据背景色自动调整头像边框颜色:
/* 头像在有深色背景的容器里 */
.dark-section:has(.avatar) .avatar {
border-color: rgba(255, 255, 255, 0.3);
}
4. 空状态
检测容器是否为空:
/* 容器没有子元素时显示空状态 */
.list-container:not(:has(> *))::after {
content: '暂无数据';
display: block;
text-align: center;
padding: 2rem;
color: #999;
}
/* 容器有子元素时隐藏空状态 */
.list-container:has(> *)::after {
display: none;
}
5. 悬停父元素高亮子元素
/* 鼠标悬停在表格行上时,整行高亮 */
tr:hover td {
background: #f0f9ff;
}
/* 更灵活的方式:鼠标在行内任意位置都高亮 */
.table-row:has(td:hover) td {
background: #f0f9ff;
}
和 JavaScript 的配合
:has() 减少了大量 DOM 操作的场景。以前需要 JS 添加/移除 class 的情况,现在可以用 CSS 直接处理。
// 以前
const card = document.querySelector('.card');
const img = card.querySelector('img');
if (img) {
card.classList.add('has-image');
}
// 现在:不需要 JS
这意味着更少的 DOM 操作,更好的性能,更少的代码。
性能考虑
:has() 的性能曾经是大家担心的点。2025 年主流浏览器的实现已经很成熟了,性能不是问题。但有几个注意事项:
- 避免过度嵌套:
:has()内部不要嵌套太深 - 避免通配符:
:has(*)会匹配所有后代,性能差 - 避免在动画中使用:
:has()的计算可能触发重排
/* 好:明确指定子元素 */
.card:has(img) { ... }
/* 差:通配符 */
.card:has(*) { ... }
/* 差:嵌套太深 */
.container:has(.item:has(.sub-item:has(.deep-item))) { ... }
浏览器兼容性
到 2025 年,所有主流浏览器都支持了 :has():
- Chrome 105+
- Safari 15.4+
- Firefox 121+
Firefox 在 2023 年底才支持,是目前需要注意兼容性的最后一个浏览器。如果需要支持旧版 Firefox,用 @supports 做渐进增强:
/* 不支持 :has() 时的降级方案 */
.card {
border: 1px solid #ddd;
padding: 1rem;
}
/* 支持 :has() 时的增强 */
@supports selector(:has(*)) {
.card:not(:has(img)) {
border: none;
padding: 0;
}
}
写在最后
:has() 是 CSS 近年来最有用的选择器之一。它让很多以前需要 JavaScript 才能实现的交互变成了纯 CSS 方案,减少了 JS 和 CSS 的耦合。项目里大量用上之后,代码确实更干净了。
CSS
返回首页