CSS :has() 不只会选父元素,还能玩出这些花样——从状态到底层布局的实战手记

2026-08-27 0 345

最近在重复开发几个后台表单的时候,被一个旧逻辑膈应到了。为了给输入框加一个“错误”状态,我得在JS里监听input事件,然后给父容器加一个class,再根据这个class去改边框颜色。接着我又得去处理清除错误、焦点状态、验证逻辑…… 繁琐得让人头大。

后来我试着用:has()去重构了一下,发现很多状态其实可以在CSS层直接“推导”出来,JS只需要触发原生事件就好。真有种脑子被打开的感觉。

从父选择器到状态选择器

:has()刚出的时候大家都叫它“父选择器”,因为它确实能让你根据子元素选中父元素。但用多了你会发现它更像个“状态依赖选择器”——只要某个元素里出现了我们关心的状态,父元素、兄弟元素都能被选中。

拿一个非常常见的表单举例:

<div class="field">
  <label for="mail">Email地址</label>
  <input type="email" id="mail" required>
  <span class="error">请输入正确的邮箱格式</span>
</div>

按照以前的思路,输入框不符合格式时,我们可能要从JS里给这个div加个“has-error”类。现在有了:has(),可以直接用:invalid状态来驱动:

<style>
  .field:has(input:invalid:not(:placeholder-shown)) {
    border-left: 3px solid #d44;
  }
  .field:has(input:invalid:not(:placeholder-shown)) .error {
    display: block;
  }
</style>

看到没?我不需要在JS里同步状态,只要用户输入了不合法内容,这一组规则就会自动命中。省掉的是加class、减class、判断时机的麻烦。

注意,我用了:placeholder-shown来排除“空值但不提示”的情况。这里纯粹是思路上的区别——我们把“数据状态”交给了浏览器,而不是交给JS来手动记录。

真正的“响应式布局”是看内容,不是看屏幕

做卡片列表时,常常会遇到“每行到底显示几个”的问题。传统做法是用媒体查询,根据屏幕宽度去猜。但是卡片里面的文字长短、图片比例、标题行数等等因素,都会影响布局效果。

我最近写了一种更贴近内容的写法:使用容器查询+子元素数量判断。容器查询负责尺寸,:has()负责“数量选择”。

假设现在有一个容器,里面可能放3张卡片,也可能放5张。我希望卡片数量多的时候让每张卡片占的宽度小一点。

<div class="card-wrap">
  <article class="card">...</article>
  <article class="card">...</article>
  <article class="card">...</article>
  <article class="card">...</article>
</div>

那么可以用这样的CSS:

<style>
  .card-wrap {
    container-type: inline-size;
    display: grid;
    gap: 1rem;
  }
  /* 默认每行1个卡片 */
  .card { grid-column: 1; }

  /* 至少3个卡片时,每行2个 */
  .card-wrap:has(.card:nth-child(3)) .card {
    grid-column: span 1;
  }
  /* 至少4个卡片时,每行3个 —— 利用容器宽度 */
  @container (min-width: 600px) {
    .card-wrap:has(.card:nth-child(4)) .card {
      grid-column: span 1;
      grid-template-columns: repeat(3, 1fr);
    }
  }
</style>

这个例子可能不是最优雅的,但我想表达的是:我们可以基于“实际渲染了多少个子元素”来调整布局,而不是预定义一堆断点。尤其适合博客标签、分类列表这种元素数量不固定的场景。

面包屑或导航栏的动态分割线

之前做导航菜单时,如果菜单项很多,我想让最后一个菜单项跟前面的视觉上区分开。通常是用:last-child,但如果菜单是动态生成的,还可能有隐藏项,就比较麻烦。

:has()能轻易地判断“某个元素后面还有没有兄弟元素”。比如我想让除最后一个以外的菜单项都要右边距:

<nav class="menu">
  <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >首页</a>
  <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >文章</a>
  <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >关于</a>
</nav>

用:has()我可以反着写:选中那些“后面还有兄弟a”的a,然后给它们加分隔线:

<style>
  .menu a:has(+ a)::after {
    content: "/";
    margin-left: 8px;
    color: #c0c0c0;
  }
</style>

这里面+是相邻兄弟选择器,配上:has()就代表“这个a后面跟着另一个a”。这个写法比:not(:last-child)直观得多,也更符合自然语言:“后面还有东西的就加斜杠”。

配合状态,做一套不用JS的暗黑模式开关

现在提到暗黑模式,大多数方案都是通过给html根节点加一个class来切换。你仍然需要写两条变量分支。但有没有想过,我们可以不切换class,而是利用本地的某个开关状态去改变整页主题?

我的思路是:在页面顶部放一个隐藏的checkbox,然后整个页面的主题由它来驱动。这一步不需要在html上加任何class,也不用JS去监听。

<input type="checkbox" id="darkModeToggle" hidden>

<body>
  <header>页面头部</header>
  <main>所有内容</main>
  <footer>充数的底部</footer>
</body>

配合:has()就能做到:

<style>
  /* 默认亮色 */
  html {
    --bg: #ffffff;
    --text: #1a1a1a;
  }
  /* 当body下某个checkbox被勾选中时,切换全局变量 */
  body:has(#darkModeToggle:checked) {
    --bg: #1a1a1a;
    --text: #f0f0f0;
  }
  body {
    background-color: var(--bg);
    color: var(--text);
  }
</style>

是不是很神奇?checkbox虽然藏起来了,但只要它被选中,整个body里的所有var()都会重新取到暗黑变量的值。这个模式很适合那种不需要记住用户偏好的小工具页面,代码量少到可怜。

当然,真正做产品暗黑模式肯定要配合localStorage和媒体查询。但这个小技巧可以让你在原型阶段快速体验暗黑效果,不用碰一行JS。

踩坑小记

:has()虽然强大,但也不是万能的。我实际用下来有几个注意点:

  • 性能上要避免在容器上添加极其复杂的:has()条件,因为浏览器会重新计算匹配范围。幸好常规业务树不大,感知不明显。
  • 伪元素本身无法被:has()直接选中,因为它不是真实元素。比如你无法写:.card:has(::after)。
  • 在Safari 17.2之后都已经支持了,但一些旧浏览器比如Chrome 105之前的版本不支持,如果用户环境较旧,还是要谨慎。

如果你最近也在做表单校验、复杂导航、自适应布局,不妨试试用:has()把状态“硬编码”进去。写完你也会发现,以前随手写的class操作确实有点多余。

今天的分享就到这里了。这个特性的热度其实从推出至今就没降过,但真正把它用起来的项目真的不多。我总觉得:“CSS越写越好,JS越写越少”才是前端未来的样子。

CSS :has() 不只会选父元素,还能玩出这些花样——从状态到底层布局的实战手记
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 css CSS :has() 不只会选父元素,还能玩出这些花样——从状态到底层布局的实战手记 https://www.taomawang.com/web/css/2654.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务