:has()
主要是用来检查一个元素是否包含特定的其他元素。它就像是简化了的条件样式。
但它不仅仅是在父子关系上的查找。:has()
很灵活。你可以用它做出创意,可以基于不同元素关系应用样式的不同方法。
:has()是什么?兼容性怎么样?
:has()
被归类为第4级CSS
选择器,并已在Chrome 105
及以上版本中实现(Firefox是最后一个支持的,但在2023年底的版本121中也加入了)。
它的引入是重要的,因为它允许在CSS
中进行关系检查,这是一个长期以来一直被需求的特性。
全球90%的使用率,且在所有现代浏览器中都可用,没有理由不在今天就开始在你的未来项目中使用它!
基础知识
示例:
这是我们生成的HTML:
<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:
.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
代码:
.header:has(h2) {
background-color: darkgrey;
}
/* added style just to center the subtitle */
.subtitle {
text-align: center;
}
当我们在HTML中添加了一个带有subtitle类的h2元素:
<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>
我们将得到一个不同颜色的头部:
父元素同时包含两个元素
当我们想要在父元素同时包含副标题和引用时进行样式设置时,该怎么办?非常简单:
.header:has(h2):has(blockquote) {
background-color: hotpink;
}
只有当同时存在h2
和blockquote
时,背景颜色才会改变。
父元素包含任一或两个元素
如果你想要在存在一个或两个元素时保持样式:
.header:has(h2, blockquote) {
background-color: lightsalmon;
}
父元素不包含某个元素
使用:not()
伪类,我们可以做相反的操作,并在父元素中不存在子元素时设置样式:
.header:not(:has(h2)) {
background-color: lightpink;
}
任何位置选择器
无JS的条件样式
根据条件,可以使用:has()
选择器在几乎任何位置选择任何东西。在下面的例子中,我们甚至可以在没有JS的情况下触发一种设置!
<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>
body:has(input.blur-answer:checked) .answer {
filter: blur(5px);
}
当我们检查输入时,answer
类将被模糊处理。
选择器的意思是,当body
元素中有一个具有blur-answer
类的输入并且它被选中时,样式如下(可以使用任何选择器)。
防止样式
“任何位置选择器”的另一个用例可能是在导航栏中删除logo
,以防logo
已经出现在您的主英雄部分中。
在这种情况下,重复出现的logo是一个眼中钉,你的设计师会大发雷霆。
我们只需要编写一个CSS选择器,检查是否有一个主英雄部分(我们的.hero-with-logo类),如果是这样,隐藏导航栏的logo:
/* ... 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()之前,这是不可能的事情。
让我们看一下:
<p>element 1</p>
<p>element 2</p>
<p class="select-before">element 3</p>
<p>element 4</p>
<p>element 5</p>
*:has(+ .select-before) {
background-color: palegoldenrod;
}
我们有5个元素,第3个元素有.select-before
类。在我们的CSS中,我们告诉浏览器给任何具有具有select-before
类的下一个兄弟元素的元素添加背景颜色。
这样,元素2将会有一个背景颜色:
我们可以用这种方法做很多古怪的事情,不过,其中一个可能有用的是突出显示无效输入的标签:
<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来创建幻灯片。根据幻灯片上的内容,我们可以切换幻灯片的布局。例如:
<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>
/* ... 通用样式 */
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;
}
我们根据内容改变模板区域。
表单验证样式
例如,我们可以在用户输入无效时为表单内的标签设置样式:
<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>
label:has(input:user-invalid) {
background-color: red;
}
请注意,两个输入都是必填项,且嵌套在标签内。这就是告诉我们选择器的标志,即标签中有一个输入是无效的。
现在,如果我们不输入符合8个字符要求的密码,或者我们不在网址输入中输入有效的URL,我们将得到一个验证信息:
再次强调,所有的用户体验交互,无需JavaScript!
我们还可以使用这种方法来为我们的输入中的一个无效时设置样式:
<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>
fieldset:has(input:user-invalid) {
border: 1px solid red;
}
与标签示例类似,关键部分是字段集中包含了无效的输入,以实现这一目标。
指定元素添加样式
如果你想要为我们没有悬停的其他元素添加样式,该怎么办?
看看这个:
<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>
.card-list:has(.card:hover) .card:not(:hover) {
opacity: 0.4;
transform: scale(0.9);
}
我们针对.card-list类,检查卡片是否被悬停,然后选择我们没有悬停的卡片。
这个方法非常酷,并且在没有:has()
之前是无法实现的。
数量查询
如果父元素有X个或更多子元素,则可以对其进行不同的样式设置。例如:
<ul class="card-list">
<li class="card">Card1</li>
<li class="card">Card2</li>
<li class="card">Card3</li>
</ul>
.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组件可能会渲染一个空的子元素,但是您很难注意到吗?
试试这个:
<article>
<p>我是一篇文章。如果我的子元素为空,我的父元素将有边框。</p>
<p></p>
</article>
article:has(> *:empty) {
height: auto;
border: 1px dotted crimson;
}
在调试时,这可能会很方便。
有下拉菜单
如果您有一个带有嵌套元素的下拉菜单,您可以找到它并标记它。
.menu-dropdown li:has(ul) b:after {
content: " ⬇️";
}
现在,我们知道哪个部分可以展开:
匹配选择器中的属性
假设您有一个带有透明背景的.png图像,您可以找到它并添加背景颜色,如下所示:
<main class="display">
<img src="image.png" />
</main>
.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()
带来了一种力量和灵活性,使设计师和开发人员都感到欣喜若狂。
正如我们所探讨的,无论是根据数量样式化元素,选择前一个元素,还是甚至以一种新发现的优雅方式处理表单验证.
网友评论