:has()使用案例

:has()主要是用来检查一个元素是否包含特定的其他元素。它就像是简化了的条件样式。

但它不仅仅是在父子关系上的查找。:has()很灵活。你可以用它做出创意,可以基于不同元素关系应用样式的不同方法。

:has()是什么?兼容性怎么样?

:has()被归类为第4级CSS选择器,并已在Chrome 105及以上版本中实现(Firefox是最后一个支持的,但在2023年底的版本121中也加入了)。

它的引入是重要的,因为它允许在CSS中进行关系检查,这是一个长期以来一直被需求的特性。

全球90%的使用率,且在所有现代浏览器中都可用,没有理由不在今天就开始在你的未来项目中使用它!

基础知识

示例:

这是我们生成的HTML:

1
2
3
4
5
6
7
8
9
<div class="main-container">
<header class="header">
<img loading="lazy" srcset="..." class="image" />
<h1 class="title">Blog Post Title</h1>
<div class="meta-data">Written by John Doe on October 10, 2022</div>
</header>
<p class="description">Lorem ipsum dolor sit amet...</p>
<a href="#" class="read-more">Read More</a>
</div>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
.main-container {
background-color: #f1f1f1;
display: flex;
flex-direction: column;
justify-content: center;
padding: 8px;
}

.header {
display: flex;
flex-direction: column;
padding: 40px 49px;
}

@media (max-width: 991px) {
.header {
max-width: 100%;
padding: 0 20px;
}
}

.image {
aspect-ratio: 1;
object-fit: contain;
object-position: center;
width: 800px;
overflow: hidden;
align-self: center;
max-width: 100%;
}

.title {
color: #000;
text-align: center;
align-self: center;
margin-top: 65px;
white-space: nowrap;
font: 400 24px Arial, sans-serif;
}

@media (max-width: 991px) {
.title {
margin-top: 40px;
white-space: initial;
}
}

.meta-data {
color: #666;
text-align: center;
align-self: center;
margin-top: 40px;
white-space: nowrap;
font: 400 15px Arial, sans-serif;
}

@media (max-width: 991px) {
.meta-data {
white-space: initial;
}
}

.description {
color: #000;
text-align: center;
align-self: stretch;
margin-top: 67px;
font: 400 16px/26px Arial, sans-serif;
}

@media (max-width: 991px) {
.description {
max-width: 100%;
margin-top: 40px;
}
}

.read-more {
color: #fff;
text-align: center;
white-space: nowrap;
border-radius: 4px;
background-color: #333;
align-self: center;
margin-top: 40px;
justify-content: center;
padding: 20px 54px;
font: 400 15px Arial, sans-serif;
}

@media (max-width: 991px) {
.read-more {
white-space: initial;
padding: 0 20px;
}
}
父元素有一个特定的元素

最简单的用例是当你想样式父元素时,它有一个特定的子元素。假设我们想用不同的方式来设计我们的博客文章,当我们有一个副标题。在这种情况下,我们将改变背景颜色。这是我们需要添加的所有CSS代码:

1
2
3
4
5
6
7
8
.header:has(h2) {
background-color: darkgrey;
}

/* added style just to center the subtitle */
.subtitle {
text-align: center;
}

当我们在HTML中添加了一个带有subtitle类的h2元素:

1
2
3
4
5
6
7
8
9
10
<div class="main-container">
<header class="header">
<img loading="lazy" srcset="..." class="image" />
<h1 class="title">Blog Post Title</h1>
<h2 class="subtitle">Subtitle</h2>
<div class="meta-data">Written by John Doe on October 10, 2022</div>
</header>
<p class="description">Lorem ipsum dolor sit amet...</p>
<a href="#" class="read-more">Read More</a>
</div>

我们将得到一个不同颜色的头部:

父元素同时包含两个元素

当我们想要在父元素同时包含副标题和引用时进行样式设置时,该怎么办?非常简单:

1
2
3
.header:has(h2):has(blockquote) {
background-color: hotpink;
}

只有当同时存在h2blockquote时,背景颜色才会改变。

父元素包含任一或两个元素

如果你想要在存在一个或两个元素时保持样式:

1
2
3
.header:has(h2, blockquote) {
background-color: lightsalmon;
}
父元素不包含某个元素

使用:not()伪类,我们可以做相反的操作,并在父元素中不存在子元素时设置样式:

1
2
3
.header:not(:has(h2)) {
background-color: lightpink;
}

任何位置选择器

无JS的条件样式

根据条件,可以使用:has()选择器在几乎任何位置选择任何东西。在下面的例子中,我们甚至可以在没有JS的情况下触发一种设置!

1
2
3
4
5
6
7
8
<body>
<p>What is the meaning of life?</p>
<p class="answer">42</p>
<label>
<input type="checkbox" class="blur-answer" />
Hide answer
</label>
</body>
1
2
3
body:has(input.blur-answer:checked) .answer {
filter: blur(5px);
}

当我们检查输入时,answer类将被模糊处理。

选择器的意思是,当body元素中有一个具有blur-answer类的输入并且它被选中时,样式如下(可以使用任何选择器)。

防止样式
“任何位置选择器”的另一个用例可能是在导航栏中删除logo,以防logo已经出现在您的主英雄部分中。

在这种情况下,重复出现的logo是一个眼中钉,你的设计师会大发雷霆。

我们只需要编写一个CSS选择器,检查是否有一个主英雄部分(我们的.hero-with-logo类),如果是这样,隐藏导航栏的logo:

1
2
3
4
5
6
/* ... other styles */

body:has(.hero-with-logo) .nav-bar .logo {
/* This can be "display: none;", however in this case that would shift the links to the left */
opacity: 0;
}

现在我们的logo只出现了一次:

元素选择器向前遍历

在有了:has()之前,这是不可能的事情。

让我们看一下:

1
2
3
4
5
<p>element 1</p>
<p>element 2</p>
<p class="select-before">element 3</p>
<p>element 4</p>
<p>element 5</p>
1
2
3
*:has(+ .select-before) {
background-color: palegoldenrod;
}

我们有5个元素,第3个元素有.select-before类。在我们的CSS中,我们告诉浏览器给任何具有具有select-before类的下一个兄弟元素的元素添加背景颜色。

这样,元素2将会有一个背景颜色:


我们可以用这种方法做很多古怪的事情,不过,其中一个可能有用的是突出显示无效输入的标签:

1
2
3
4
5
6
7
8
9
<label for="url-input">请输入网址:</label>
<!-- 当设置为“url”类型时,您的浏览器将处理验证和用户无效状态 -->
<input type="url" id="url-input" />

<style>
label:has(+ #url-input:user-invalid) {
border: 1px solid red;
}
</style>

通过这4行代码,我们可以得到原生HTML验证、有条件的样式,而无需JS!

布局定位

假设我们使用HTML和CSS来创建幻灯片。根据幻灯片上的内容,我们可以切换幻灯片的布局。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<article>
<h1>Hello slide with h1!</h1>
</article>

<article>
<h2>Hello slide with h2 and img!</h2>
<img class="img1" src="https://picsum.photos/600" />
</article>

<article>
<h2>Hello slide with h2 and 2 imgs!</h2>
<img class="img2" src="https://picsum.photos/200" />
<img class="img3" src="https://picsum.photos/200" />
</article>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* ... 通用样式  */

article:has(> is:(h1, h2):only-child) {
grid-template-areas:
"heading heading"
"heading heading";
}

article:has(h2 + img) {
grid-template-areas:
"heading heading"
"image1 image1";
}

article:has(h2 + img + img) {
grid-template-areas:
"heading heading"
"image2 image3";
place-content: center;
}

我们根据内容改变模板区域。

表单验证样式

例如,我们可以在用户输入无效时为表单内的标签设置样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
<form>
<div>
<label for="pass">密码:
<input id="pass" type="password" minlength="8" required/>
</label>
</div>
<div>
<label for="website">网址:
<input id="website" type="url" required/>
</label>
</div>
<button>提交</button>
</form>
1
2
3
label:has(input:user-invalid) {
background-color: red;
}

请注意,两个输入都是必填项,且嵌套在标签内。这就是告诉我们选择器的标志,即标签中有一个输入是无效的。

现在,如果我们不输入符合8个字符要求的密码,或者我们不在网址输入中输入有效的URL,我们将得到一个验证信息:

再次强调,所有的用户体验交互,无需JavaScript!

我们还可以使用这种方法来为我们的输入中的一个无效时设置样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form>
<fieldset>
<legend>添加您的信息</legend>
<div>
<label for="pass">密码:
<input id="pass" type="password" minlength="8" required />
</label>

</div>
<div>
<label for="website">网址:
<input id="website" type="url" required />
</label>
</div>
<button>提交</button>
</fieldset>
</form>
1
2
3
fieldset:has(input:user-invalid) {
border: 1px solid red;
}

与标签示例类似,关键部分是字段集中包含了无效的输入,以实现这一目标。

指定元素添加样式

如果你想要为我们没有悬停的其他元素添加样式,该怎么办?

看看这个:

1
2
3
4
5
6
7
<ul class="card-list">
<li class="card">Card1</li>
<li class="card">Card2</li>
<li class="card">Card3</li>
<li class="card">Card4</li>
<li class="card">Card5</li>
</ul>
1
2
3
4
.card-list:has(.card:hover) .card:not(:hover) {
opacity: 0.4;
transform: scale(0.9);
}

我们针对.card-list类,检查卡片是否被悬停,然后选择我们没有悬停的卡片。

这个方法非常酷,并且在没有:has()之前是无法实现的。

数量查询

如果父元素有X个或更多子元素,则可以对其进行不同的样式设置。例如:

1
2
3
4
5
<ul class="card-list">
<li class="card">Card1</li>
<li class="card">Card2</li>
<li class="card">Card3</li>
</ul>
1
2
3
4
5
6
7
8
9
10
11
12
.card-list:has(> *:nth-child(3n)) {
display: grid;
grid-template-columns: 1fr 1fr;
place-items: center;
gap: 1rem;
background-color: lightgrey;
& :last-child {
width: 50vw;
background-color: royalblue;
grid-column: 1 / -1
}
}

空的子元素

您是否曾经遇到过这样的情况:您的React组件可能会渲染一个空的子元素,但是您很难注意到吗?

试试这个:

1
2
3
4
<article>
<p>我是一篇文章。如果我的子元素为空,我的父元素将有边框。</p>
<p></p>
</article>
1
2
3
4
article:has(> *:empty) {
height: auto;
border: 1px dotted crimson;
}

在调试时,这可能会很方便。

有下拉菜单

如果您有一个带有嵌套元素的下拉菜单,您可以找到它并标记它。

1
2
3
.menu-dropdown li:has(ul) b:after {
content: " ⬇️";
}

现在,我们知道哪个部分可以展开:

匹配选择器中的属性

假设您有一个带有透明背景的.png图像,您可以找到它并添加背景颜色,如下所示:

1
2
3
<main class="display">
<img src="image.png" />
</main>
1
2
3
.display:has(img[src$=".png"]) img {
background: linear-gradient(90deg, rgba(232,231,232,1) 0%, rgba(201,204,208,1) 100%);
}

您可以使用这种方法来获取基本上您能想到的任何属性,并为父元素或该父元素的任何子元素设置样式。请随意提出您的用例。

结论

在网页开发的大背景中,引入:has()伪类就像在CSS地图上发现了一个新大陆一样,为我们以前只能梦想的动态样式提供了无限可能。

从根据其子元素的属性为父元素添加样式,到在没有一行JavaScript的情况下实现复杂的布局更改,:has()带来了一种力量和灵活性,使设计师和开发人员都感到欣喜若狂。

正如我们所探讨的,无论是根据数量样式化元素,选择前一个元素,还是甚至以一种新发现的优雅方式处理表单验证.