CSS 选择器选择前一个兄弟元素
CSS 中不能直接通过后一个元素选择前一个元素,只能通过前一个元素选择后一个元素,类似 + 和 ~。
但是 CSS 提供了一种针对引用元素选择父元素或者先前的兄弟元素的方法。就是 :has 选择器。
:has 是一个函数式伪类选择器,用于选择满足条件的锚定元素,这里的锚定元素指的是使用 :has 的元素。
例如现在有两个元素
<div class="first"></div>
<div class="second"></div>
想要实现:在鼠标移入 second 元素时,first 元素对应的背景修改为红色。
就可以使用 :has 这样写
.first:has(+ .second:hover) {
background-color: red;
}
这样巧妙把问题转换为:选择满足条件的 first 元素,这里的条件就是:后面紧跟一个鼠标悬浮态的 second 元素。
:has 是一个函数式伪类,所以我们可以把它看作是一个函数,那么是函数的话就可以传递参数。
:has 接收的参数就是一个可容错相对选择器列表。
什么是可容错相对选择器列表?
这就得先从选择器列表开始说起。
在CSS中,选择器列表指的是用逗号分隔的多个选择器,例如 div, p 。这在我们给多个元素设置相同的样式时非常方便。
/* 原来要这样写 */
div{
color: red;
}
p{
color: red;
}
/* 使用选择器列表可以共享相同的声明 */
div, p {
color: red;
}
但是选择器列表有一个问题,就是如果列表中的任何一个选择器无效,那么整个列表就无效。
/* 我们使用了不存在的detail标签 */
div, div>detail, p {
color: red;
}
这时候 div 和 p 标签的样式就不会生效。
为了解决这个问题,CSS 引入了可容错选择器列表的概念。 即使其中一个选择器无效,整个列表也会生效。像 :is() 或 :where() 伪类 它们就接受一个可容错选择器列表作为参数。
:is(div, div>detail, p) {
color: red;
}
即使 div>detail 这个选择器无效,div 和 p 标签的样式也会应用样式。
那可容错相对选择器列表的“相对”又是什么意思?
其实就是说这个CSS选择器,它不是从文档根开始找,而是相对于某个‘锚点’元素(也就是你应用 :has() 的那个元素)来查找其内部的元素。
再反过头来看可容错相对选择器列表,我们就可以给它下个结论性的定义:
可容错相对选择器列表就是可以接受错误,并且相对于锚定元素查找的选择器列表。
现在知道了 :has 的基本使用后,我们可以根据其特点用在一些场景中。
<div class="input-group">
<label for="email">Email*</label>
<input type="email" id="email" required>
<span class="error-message">请输入有效的邮箱地址</span>
</div>
/* 当input无效且获得焦点时,高亮其父容器 */
.input-group:has(input:invalid:focus) {
border-color: red;
background-color: #ffe6e6;
}
/* 同时显示错误信息 */
.input-group:has(input:invalid:focus) .error-message {
display: block;
}
<div class="input-group">
<label for="email">Email*</label>
<input type="email" id="email" required>
</div>
/* 输入框获得焦点时,将其对应的标签变为蓝色 */
.input-group:has(input:focus) label {
color: #0066cc;
}
根据子元素数量或类型动态改变布局。
<div class="card-grid">
<div class="card">...</div>
<div class="card">...</div>
<div class="card large">...</div> <!-- 一个大卡片 -->
</div>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
/* 如果网格中包含 .large 卡片,则改为两列布局 */
.card-grid:has(.card.large) {
grid-template-columns: 1fr 1fr;
}
当鼠标悬停在 .second 元素的 p 标签上时,.first 元素的 p 标签会变成红色。
<div class="first">
<p>我是p1</p>
</div>
<div class="second">
<p>我是p2</p>
</div>
.first:has(+ .second>p:hover) > p {
color: red;
}
需要注意的是:在 :has() 函数内部,不能再嵌套使用 :has()。
这主要是出于性能考量。:has() 本身就是 CSS 中最复杂、最耗性能的选择器之一,因为它打破了浏览器“从左到右”解析 CSS 选择器的常规模式。
浏览器通常的解析方式是:
div > p -> 先找到所有 div,然后在每个 div 里找 p。
而 :has() 的解析逻辑更像是:
div:has(p) -> 先找到所有包含 p 的 div。这需要浏览器做更多“回溯”或“记录”的工作。
如果允许嵌套,比如 :has(:has(:has(...))),会形成巨大的递归查询,计算复杂度会呈指数级增长,可能会使页面样式计算变得极其缓慢,甚至导致浏览器卡死。