: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; }
只有当同时存在h2
和blockquote
时,背景颜色才会改变。
父元素包含任一或两个元素 如果你想要在存在一个或两个元素时保持样式:
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 (3 n)) { display : grid; grid-template-columns : 1 fr 1 fr; 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()
带来了一种力量和灵活性,使设计师和开发人员都感到欣喜若狂。
正如我们所探讨的,无论是根据数量样式化元素,选择前一个元素,还是甚至以一种新发现的优雅方式处理表单验证.