CSS :has() 与容器查询实战:不写 JS 的组件响应式

举报
yd_225105208 发表于 2026/11/03 20:03:14 2026/11/03
【摘要】 媒体查询看的是视口宽度,不是组件所在容器的宽度。同一个卡片组件放在侧栏和主区域,需要两种布局,但视口宽度是一样的——媒体查询没法区分。:has() 让选择器能根据子元素状态选父元素,容器查询让组件能根据自身容器宽度调整布局。两者合起来,能做到以前必须写 JS 才能实现的响应式效果。一、:has() 实战1. 表单状态联动以前要给父容器加高亮,得监听 checkbox 的 change 事件再...

媒体查询看的是视口宽度,不是组件所在容器的宽度。同一个卡片组件放在侧栏和主区域,需要两种布局,但视口宽度是一样的——媒体查询没法区分。:has() 让选择器能根据子元素状态选父元素,容器查询让组件能根据自身容器宽度调整布局。两者合起来,能做到以前必须写 JS 才能实现的响应式效果。

一、:has() 实战

1. 表单状态联动

以前要给父容器加高亮,得监听 checkbox 的 change 事件再切 class。现在一行 CSS 搞定:

.card:has(input[type="checkbox"]:checked) {
  border-color: #3b82f6;
  background: #eff6ff;
}

/* 有错误提示时,输入框变红 */
.field:has(.error) input {
  border-color: #ef4444;
}

2. 有无图片的卡片自适应

/* 有图片:横向布局 */
.card:has(img) {
  display: grid;
  grid-template-columns: 120px 1fr;
}

/* 无图片:纯文本,多留点内边距 */
.card:not(:has(img)) {
  padding: 1.5rem;
}

3. 选前面的兄弟元素

组合器只能选后面的兄弟,:has(+ selector) 可以反向选:

/* 标题后面紧跟表格时,收紧下边距 */
h2:has(+ table) {
  margin-bottom: 0.5rem;
}

二、容器查询实战

先声明容器,告诉浏览器这个元素要作为查询基准:

.card-grid {
  container-type: inline-size;
  container-name: card;
}

再用 @container 按容器宽度设置断点:

@container card (max-width: 400px) {
  .card { grid-template-columns: 1fr; }
}

@container card (min-width: 600px) {
  .card { grid-template-columns: 200px 1fr; }
}

同一个组件放进 320px 的侧栏就堆叠,放进 800px 的主区域就并排,不需要组件知道自己被放在哪。

三、组合用法

容器宽度决定断点,:has() 决定内容形态,两个判断互相独立:

.card-wrap { container-type: inline-size; }

@container (max-width: 320px) {
  .card h3 { font-size: 1rem; }
  .card .meta { display: none; }
}

@container (min-width: 480px) {
  .card { display: grid; grid-template-columns: 160px 1fr; }
  .card:not(:has(img)) { grid-template-columns: 1fr; }
}

有图的卡片横向排,无图的单列;窄容器里隐藏次要信息。全部由 CSS 判断,React/Vue 组件里一行状态逻辑都不用加。

四、兼容性与降级

  • :has():Chrome 105+、Safari 15.4+、Firefox 121+
  • @container:Chrome 105+、Safari 16+、Firefox 110+

用 @supports 包一层,老浏览器走默认布局,不会白屏:

@supports selector(:has(*)) {
  .card:has(img) { /* 增强布局 */ }
}

五、什么时候还得写 JS

三种情况:跨组件的状态依赖、需要读取真实像素值参与计算、和 Canvas 或图表库联动。单纯的布局开关交给 CSS 更省事,SSR 时也不会先闪一下错误布局。

小结

媒体查询回答「设备有多宽」,容器查询回答「我所在的容器有多宽」,:has() 回答「我的子元素处于什么状态」。三个维度互补,覆盖了大多数以前要用 ResizeObserver 或事件监听才能做的响应式需求。  

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。