`,在输入框获得焦点时显示蓝色边框,如果输入框的值为空且处于 `:invalid` 状态(比如设置了 `required`),则显示红色边框和提示文字。
原有做法是为每个 `form-group` 绑定 `focus`、`blur` 和 `input` 事件,动态切换 `focused` 和 `error` 类。重构后,样式可以直接写成:
.form-group {
border: 2px solid #ccc;
padding: 12px;
border-radius: 6px;
transition: border-color 0.2s;
}
/* 当内部 input 获得焦点时,父容器高亮 */
.form-group:has(input:focus) {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59,130,246,0.2);
}
/* 当内部 input 处于无效状态(如 required 为空)时显示错误 */
.form-group:has(input:invalid) {
border-color: #ef4444;
}
/* 结合无效状态下的 placeholder-shown 或 value 为空来精确控制 */
.form-group:has(input:invalid:not(:placeholder-shown)) {
border-color: #ef4444;
}
对应的 HTML 片段:
<div class="form-group">
<label for="email">邮箱地址</label>
<input type="email" id="email" required placeholder="请输入邮箱">
<span class="error-msg">请输入有效的邮箱</span>
</div>
错误提示文字的显示与隐藏同样可以用 `:has()` 结合兄弟选择器控制:
.error-msg {
display: none;
color: #ef4444;
font-size: 0.875rem;
}
.form-group:has(input:invalid:not(:placeholder-shown)) .error-msg {
display: block;
}
这样,当用户点击输入框离开后,如果内容不符合 `email` 类型或者留空,错误信息自动显示,父容器变红。整个过程不再需要一行 JS 去检测表单状态,浏览器内置的约束验证 API(`:valid`/`:invalid`)与 `:has()` 配合天衣无缝。这也意味着表单验证的视觉反馈逻辑从命令式变成了声明式,代码量骤减且可维护性提升。
三、场景二:卡片组件内部状态驱动外部样式
另一类常见需求是:当一个卡片内部包含一个选中的复选框时,卡片本身需要改变外观,比如出现一个勾选标记或者背景色变淡蓝以示选中。以前需要在复选框 `change` 事件里拿到父级卡片,然后做类名操作。现在一行 CSS 解决。
.card {
border: 1px solid #e5e7eb;
padding: 16px;
border-radius: 8px;
position: relative;
}
/* 当卡片内部的复选框被勾选时 */
.card:has(input[type="checkbox"]:checked) {
background-color: #eff6ff;
border-color: #3b82f6;
}
/* 添加一个选中的勾选标记 */
.card:has(input[type="checkbox"]:checked)::after {
content: "✓";
position: absolute;
top: 8px;
right: 12px;
font-size: 1.2rem;
color: #3b82f6;
font-weight: bold;
}
HTML 结构:
<div class="card">
<input type="checkbox" id="item1">
<label for="item1">选择项目 A</label>
<p>项目描述...</p>
</div>
这种模式在批量操作列表、多选表格行等场景中极其实用。开发者只需要关心 HTML 结构和基础的样式定义,选中状态的视觉变化完全交给 CSS 去自动管理。当需要调整选中样式时,再也不用去翻 JS 文件里那些长长的 `classList` 切换了。
四、场景三:搜索过滤时父容器的条件展示
在搜索页面中,常常需要根据输入的关键词过滤列表项,不匹配的项隐藏。传统做法是用 JS 遍历列表项进行比对后设置 `display:none`。但如果过滤逻辑相对简单(比如仅按文本包含判断),我们可以结合 `:has()` 和 `textContent` 的变通方法实现一定程度的静态过滤,尽管完全替代 JS 过滤仍有限制,但可以用于一些简单的状态切换,例如隐藏所有不包含特定标记元素的容器。
更实用的场景是:当搜索结果为空时,显示一个“没有找到相关结果”的提示。这个提示可以用 CSS 来控制,基于列表容器内部是否还有可见的列表项。
.no-results {
display: none;
color: #6b7280;
}
/* 当文章列表内没有任何可见的 article 时,显示无结果提示 */
.articles:not(:has(article:not([hidden]))) ~ .no-results {
display: block;
}
配合 JS 负责简单的 `hidden` 属性切换(这仍然是一小段 JS,但避免了复杂的类名管理),这样提示的显示逻辑就和列表项的状态保持同步了。
五、一些容易忽略的细节和限制
首先,`:has()` 不能嵌套在另一个 `:has()` 内部,即不允许 `:has(:has(…))` 这样的写法,浏览器会直接忽略该规则,这是为了避免性能问题。其次,虽然 `:has()` 功能强大,但仍不能直接选择父元素的父元素(即祖先选择器),例如 `div:has(> p:has(span))` 是允许的,但只能向上“看”一层,逻辑上是先找包含特定后代的元素,而不是任意层级向上回溯。
在性能方面,浏览器对 `:has()` 进行了大量优化,正常使用不会导致明显卡顿,但应避免在非常宽泛的选择器上使用,比如 `body:has(a)` 这种,因为任何链接的加入都会导致重新匹配。实际开发中,限定在表单容器、卡片等具体组件范围即可。
兼容性方面,`:has()` 已经在 Chrome 105+、Edge 105+、Safari 15.4+、Firefox 121+ 中全面支持,可以说覆盖了绝大多数现代浏览器。对于少数需要支持旧版浏览器的项目,可以使用 `@supports` 进行回退,但以目前后台系统主要面向最新浏览器的情况,基本上可以放心使用。
用 `:has()` 重构完这个后台系统之后,我感受到了和当年从 `float` 转到 `flexbox` 类似的爽快感。以前必须通过 JavaScript 转一道弯才能实现的“子元素影响父元素”逻辑,现在直接写在样式表里,代码的意图也更加透明。表单的状态、复选框的选中、过滤结果的反馈,这些原本分散在多个 JS 模块中的担忧,现在可以集中由 CSS 控制,开发效率和后期调整的灵活性都上了个台阶。
如果你的项目里也有大量针对父元素的类名操作,尤其是在表单密集的管理后台,可以考虑抽一个周末把最烦人的那部分换成 `:has()`。一旦用上,你可能会像我一样,很难再回到那个在 `addEventListener` 里追逐 `parentNode` 的日子。
版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。
淘吗网 css CSS :has()选择器实战:用原生父选择器重构表单验证与状态联动样式 https://www.taomawang.com/web/css/2344.html