Skip to content
文章摘要

CSS :has() 伪类选择器的妙用

前言

前几天水群,有大佬提到 CSS 的 :has() 伪类选择器可以替代 JS 的一些功能,当时感到很震惊,于是学习了一番,写下本文记录一下。

顾名思义, A:has(B) 就是指选中一个和 B 标签相关位置关系的标签 A,例如 p:has(+h1) 就是指选中这样的 p 标签,他的后面有一个相邻的 h1 标签。

下面的代码中,只有第一个 p 标签会被选中。+p 是相邻兄弟选择器。

html
<style>
  p:has(+h1) {
    color: red;
  }
</style>

<p>text text</p>
<h1>hello world</h1>
<p>text text</p>

再看一个例子,这里的 div:has(>span) 是这样一个 div,它有一个直接后代的 span,第一个 div 是不满足的,所以选中的是 <div><span>222</span></div>>span 是直接子代选择器)。

html
<style>
  div:has(>span) {
    color: red;
  }
</style>

<div><p><span>111</span></p></div>
<div><span>222</span></div>

这里不再叙述 :has() 的基础语法了,不了解的可以去自行搜索。本文主要叙述使用 :has() 代替 JS 的一些操作:

  • 点击按钮切换样式
  • 表单输入格式验证提示

点击按钮切换样式

需求1:点击按钮,修改标题的颜色。

使用 JS 很容易能够实现这个功能,但现在试着使用 CSS 实现。一开始的思路是使用 :active 配合 :has(),当具有一个相邻的被点击的标签的时候,选中目标标签。这样就模拟了点击效果,核心代码就是 h1:has(+ button:active)

html
<style>
  h1:has(+ button:active) {
    color: red;
  }
</style>

<h1>I love CSS</h1>
<button>toggle</button>

但是这个效果有问题,只有按住点击的时候(active 状态),才会选中目标标签 h1。有没有一种可以维持住某个状态的 CSS 伪类?可以想到使用 input:checkbox:checked 伪类。原理就是将 label 和 input 绑定,然后隐藏 input,点击 label 后就相当于选中了 checkbox 表单。

html
<style>
  h1:has(+ input[type=checkbox]:checked) {
    color: red;
  }
  #toggle {
    display: none;
  }
</style>

<h1>I love CSS</h1>
<input type="checkbox" id="toggle">
<label for="toggle">
  toggle
</label>

效果如下:点击 toggle 可以修改标题颜色。 demo1

表单输入格式验证提示

需求2:检测表单输入邮箱,检查输入内容格式给出用户提示。

要实现这个这个需求,我们需要判断显示不同信息的条件,例如显示一个格式非法提醒:

  1. input 不是 focus 状态(使用 :not(:focus) 判断)
  2. input 有输入内容(若没有输入内容,则显示 placeholder 内容,可以通过 :placeholder-shown 判断)
  3. 格式非法(输入邮箱不符合基本格式,使用 :invalid 伪类实现)
html
<style>
  label:has(+ :not(:focus):not(:placeholder-shown):invalid) {
    color: red;
  }
  label:has(+ :not(:focus):not(:placeholder-shown):invalid)::before {
    content: "";
  }

  label:has(+ :not(:focus):not(:placeholder-shown):valid) {
    color: green;
  }
  label:has(+ :not(:focus):not(:placeholder-shown):valid)::before {
    content: "";
  }
</style>

<form>
  <label for="email">E-mail</label>
  <input type="email" name="email" id="email" placeholder=" "/>
</form>

解释第一行 CSS 代码,其他类似。对于 label:has(+ :not(:focus):not(:placeholder-shown):invalid) 这行代码,可以按照上述三个条件理解,即 “没有 focus”、“不是显示 placeholder”、“格式非法”,那么就显示 “❌ E-mail”(红色字体)。

demo2

格式正确就显示 “✅ E-mail”:

demo3

:has 伪类是比较新的 CSS 特性,在新版本的 chrome 中可以使用,在 firefox 中要开启实验性功能才能起效果,相信未来会全面扩展。

caniusehas

https://caniuse.com/?search=%3Ahas