CSS:has()
选择器允许我们根据一个元素的后代或任何后续元素来确定其样式。在本教程中,我们将讨论何时以及如何使用:has()
选择器。
我们将讨论的内容。
何时使用CSS:has()
通常,当我们编写针对HTML元素的CSS规则时,浏览器的引擎会从右到左评估这些规则来应用它们。例如,下面的CSS规则针对的是<section>
中的<p>
元素。
section p {
color: red;
}
现在,假设我们想根据一个元素的内容或后续元素来锁定该元素--例如,锁定一个包含<p>
元素的<section>
元素。通常情况下,我们会手动添加CSS类,对目标元素进行样式和修改。
另一种情况是针对一个元素的前几个兄弟姐妹,比如一个有效或无效输入前的表单标签。
在这种情况下,为标签设计样式将涉及编写更多的CSS规则,而不是通常所需要的。有时,我们可能需要JavaScript根据某些元素的状态来定位前面的元素。
然而,通过:has()
伪类选择器,我们可以用CSS更干净、更清晰、更简洁地实现上述所有任务。
CSS:has()
选择器的浏览器兼容性
在深入研究之前,我们先看看哪些浏览器与CSS选择器兼容 。 [:has](https://caniuse.com/css-has)
选择器。
目前,CSS:has()
选择器只在最新的Safari浏览器上默认启用。其他浏览器还没有启用对它的支持。
然而,在最新的Chrome版本中,我们可以通过实验性功能标志启用对CSS:has()
的支持。就本教程而言,我们将从这一点开始。
如何在Chrome浏览器中启用CSS:has()
支持
对于那些没有使用Safari浏览器的人来说,让我们打开Chrome浏览器,在浏览器中输入以下内容访问Chrome的实验性功能列表:chrome://flags/。
然后,搜索 "实验性网络平台功能",启用这个标志,如下图所示。
启用实验性网络平台功能后,重新启动浏览器以激活它们。
CSS:has()
语法
:has()
伪类接受一个CSS选择器列表作为参数。
<target>:has(<selector>)
像其他一些CSS伪类一样,选择器列表是 "宽容的"。换句话说,CSS:has
忽略了作为参数传递的任何无效的选择器。
CSS:has()
选择器示例
在我们将这个选择器应用于真实世界的场景之前,让我们先熟悉一下如何使用CSS:has()
。我们将重新审视我们先前使用的样式规则的例子,看看:has()
是如何让我们针对一个父元素。
在下面这个使用:has()
选择器的规则中,我们针对的是一个有一个<p>
元素作为子元素的<section>
元素。
section:has(p) {
color: red
}
在上面的规则中,我们看到section
是风格color: red
适用的目标元素,但只有当它包含一个p
元素的时候。
将CSS组合器选择器作为参数传入:has()
到目前为止,我们只在:has()
中传递简单的选择器作为参数。然而,我们也可以使用更高级的CSS选择器。
例如,我们也可以把一些有规律的组合器选择器作为参数来传递。下面,下面的选择器匹配有段落元素作为兄弟姐妹的h2
元素。
h2:has(+ p) {
color: yellow;
}
与简单的CSS选择器不同,h2 + p
将匹配紧跟在h2
的p
元素。
当在:has()
中把组合器选择器作为参数传递时,我们必须了解它们是如何工作的。让我们再看几个例子。
在下面的代码中,选择器匹配那些有一个段落紧跟另一个段落的列表项。
li:has(p + p) {
color: grey;
}
下面的选择器匹配直接包含一个span
子项的p
元素。
p:has(> span) {
color: blue;
}
结合CSS:has()
和:not()
伪类
有时,我们可能想针对那些不符合某些选择器的元素。在这种情况下,我们可以使用:has()
选择器与 [:not()](https://developer.mozilla.org/en-US/docs/Web/CSS/:not)
伪类。在下面的例子中,该规则针对不包含任何段落的li
元素。
li:not(:has(p)) {
font-weight: 700;
}
我们还可以将多个选择器作为参数传递。下面,该规则针对不包含任何段落或span
元素的li
元素。
li:not(:has(p, span)) {
font-weight: 700;
}
CSS:has()
选择器的特殊性
有时,我们在使用:has()
选择器时可能会遇到规则冲突的问题。了解CSS如何处理其整体选择器的特殊性,可以帮助你解决与CSS规则冲突的问题。
让我们看一下下面的代码块。
这里是我们正在设计的HTML元素。
<ul>
<!-- other items -->
<li>
<h3 id="h3">Heading III</h3> <!-- blue color -->
<p class="cls">paragraph</p> <!-- blue color -->
</li>
</ul>
这里是我们在CSS中的样式规则。
li:has(p, #h3) {
color: blue; /* this declaration is applied */
}
li:has(p.cls) {
color: green;
}
我们可能期望在级联中最后出现的样式规则将被应用于p
。但实际上,在这个例子中,第一个规则被应用。
这是因为CSS中的特殊性--:has()
选择器服从于它最具体的参数。在上面的规则中,#h3
是最具体的,因此使浏览器也将其样式声明应用于p
元素。
CSS:has()
选择器的实际例子
为了了解如何在现实世界的项目中实现:has()
伪类,让我们考虑以下的用例。
用一个特定的子元素来设计父元素的样式:建立定价卡
下面的设计在展示定价计划的网站上很常见。注意到 "推荐 "卡的风格不同,并按比例放大以吸引更多的注意力。
我们可以使用:has()
选择器轻松实现这一点。
下面是这些卡片的HTML标记。我们将对包含嵌套的recommend
类的卡片进行不同的样式设计。
<section class="card-container">
<div class="pricing-card">
<!-- card content -->
</div>
<div class="pricing-card">
<div class="recommend">Recommended</div>
<!-- card content -->
</div>
<div class="pricing-card">
<!-- card content -->
</div>
</section>
注意,为了简洁起见,我们在上面的代码中删除了卡片的内容。请看CodePen上的完整标记。
然后,使用基本样式,卡片看起来是这样的。
通过:has()
选择器,我们可以对只有recommend
类为子的卡片进行定位和样式设计。
.pricing-card:has(div.recommend) {
box-shadow: 0 0 0 2px #4b4bf0, 0 1rem 2rem rgba(0, 0, 0, 0.1);
transform: scale(1.1);
z-index: 1;
}
上面的规则以推荐的卡片为目标,并按照预定的比例进行缩放,同时也给它施加了一个下拉阴影。
如果我们想在不使用:has()
选择器的情况下实现这种设计,我们将不得不在 "推荐的 "卡片容器上手动应用一个单独的类,使其具有不同的样式。这样做的缺点是,我们必须在每个使用相同设计的部分都添加这个类。
让我们继续前进。如果我们看一下最终的项目,推荐卡的 "立即购买 "按钮的样式也与其他两个卡不同。为了实现这一点,我们将在:not()
,同时使用:has()
选择器,像这样。
.pricing-card:not(:has(.recommend)) a {
color: #000;
background: #d5ddeb;
}
在上面的代码中,我们针对那些没有recommend
类的卡片里面的按钮作为一个子类,给它们不同的文字和背景颜色。
为前面的兄弟姐妹设计样式:有效/无效的输入标签
在我们的下一个例子中,我们将根据其对应的输入的有效性来设计输入标签的样式。我们的最终结果将是这样的。
让我们开始吧。下面的代码在一个form
元素内渲染了表单控件。
<form>
<div>
<label for="email">Email: </label>
<input type="email" id="email" required/>
</div>
<!-- other input -->
</form>
注意,为了简洁起见,我们在上面的代码中只显示一个输入容器。请看CodePen上的完整标记。
像之前的项目一样,让我们看看我们如何在不使用:has()
选择器的情况下针对输入标签。
记住,我们前面说过,浏览器通常从右边评估样式规则。出于这个原因,我们可以把label
放在input
元素的后面。
<div>
<input type="email" id="email" required/>
<label for="email">Email: </label>
</div>
然后,我们可以使用一般的~
或相邻的+
兄弟姐妹组合器来选择和设计标签。
input:invalid ~ label,
input:invalid ~ label::before {
content: '
之后,我们将使用CSSposition: absolute;
,将标签放回输入的上方。
form > div {
position: relative;
margin-bottom: 30px;
}
label {
position: absolute;
top: -30px;
left: 0;
right: 0;
}
然而,有了:has()
选择器,我们就不必把标签放在输入的后面或使用CSSposition: absolute;
声明。我们可以像这样轻松地针对前面的兄弟姐妹。
label:has(+ input:invalid),
label:has(+ input:invalid)::before {
content: '
在第一个和第二个代码块中,我们分别针对有无效输入和有效输入的label
,作为下一个兄弟姐妹。
正如我们所看到的,使用:has()
选择器使我们的代码更清晰、更简洁。请看CodePen上的完整代码。
总结
在本教程中,我们学习了:has()
选择器如何让我们根据它的后代或后继元素的实际例子来设计一个元素的样式。这个选择器开启了许多在CSS中难以实现的可能性。
目前,CSS:has()
选择器没有得到浏览器的广泛支持;这个选择器只在最新版本的Safari浏览器中工作,或者通过最新版本的Chrome浏览器的实验性功能标志工作。
因此,目前我们不能在生产中使用:has()
。我们只能在等待其他浏览器支持它的同时探索它的工作原理。
我希望你喜欢阅读这个教程。如果你有问题或贡献,请在评论区分享你的想法,并记得在网络上分享这个帖子。