CSS容器查询写起来有多爽?组件化响应式从此不再靠@media

2026-08-08 0 576

以前写响应式布局,我第一反应是打开devtools拖窗口,然后开始写@media。但最近在做一个老项目重构时我发现了一个问题:组件在侧边栏和主内容区里宽度不一样,但页面整体宽度是一样的,这时候@media完全帮不上忙。要么复制一套样式,要么用flex硬凑。

直到我把目光转向CSS容器查询。这东西不是新东西了,但最近几个版本浏览器支持得越来越顺滑,我觉得是时候正式上手了。今天就用一个实际案例,把容器查询的用法掰开揉碎讲一讲。

从一个日常翻车场景说起

想象你有一个商品卡片组件 product-card,里边包括一张图、一行标题、一个价格、一个按钮。这个卡片有时候被放在侧边栏(宽260px),有时候被放在详情页的内容区(宽800px)。正常情况下你希望卡片组件自己知道“我现在空间很窄,只能竖着排;现在很宽,可以横着排”。

但如果你只用@media查询,你只能判断整个视口的宽度,没法判断父容器给组件多少空间。于是你只好给侧边栏里的卡片单独写一套覆盖样式,一旦以后改动布局,卡片表现可能炸。

容器查询就是来解决这个问题的。它可以让组件基于自己所在的“容器”尺寸来做样式适配,而不是基于视口。

容器查询基本语法

先看最简单的写法:

.Card {
  container-type: inline-size;
}

这一句的意思是把当前元素标记为“容器”,并且只让我关注它的内联轴尺寸(对水平书写模式来说就是宽度)。以后,它的子元素就可以通过 @container 查询到这个容器的宽度了。

然后在子元素里写:

.Card-content {
  display: grid;
  gap: 0.5rem;
}

@container (min-width: 400px) {
  .Card-content {
    grid-template-columns: 1fr 1fr;
  }
}

核心概念就两个:container-type 把元素变成查询容器,@container 在子元素中通过容器尺寸条件来切换样式。和@media的区别就是“衡量标准”变了,整个思路从“视口响应”变成了“容器响应”。

给容器起个名字吧

页面里可能有很多容器,如果不加限制,@container 会沿着DOM向上找最近的容器。通常够用。但如果你想明确指定查询哪个容器,可以给容器起名:

.ProductGrid {
  container-name: product-layout;
  container-type: inline-size;
}

容器名和容器类型可以简写为:

.ProductGrid {
  container: product-layout / inline-size;
}

然后查询时指定名字:

@container product-layout (min-width: 500px) {
  .Card {
    flex-direction: row;
  }
}

这种命名方式在组件嵌套很深的时候特别好用。比如卡片内部还有一个小列表,列表可以基于卡片内部宽度做二次自适应,完全不会互相干扰。

实战:做一个真正的“自我适应”商品卡片

我直接写一个例子。假设我们有HTML结构:

<div class="ProductWrapper">
  <div class="Card">
    <img class="Card-thumb" src="https://picsum.photos/seed/think/300/200" alt="商品图">
    <div class="Card-body">
      <h3 class="Card-title">无线降噪耳机</h3>
      <p class="Card-desc">续航30小时,支持主动降噪,新配色灰金色。</p>
      <div class="Card-bottom">
        <span class="Card-price">¥499</span>
        <button class="Card-btn">加入购物车</button>
      </div>
    </div>
  </div>
</div>

然后编写样式。先把ProductWrapper定义为容器:

.ProductWrapper {
  container-type: inline-size;
}

默认情况卡片是垂直排列:图片在上,文字在下。

.Card {
  display: flex;
  flex-direction: column;
  background: #f7f7f8;
  border-radius: 16px;
  overflow: hidden;
}

.Card-thumb {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}

.Card-body {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.Card-title {
  font-size: 1.25rem;
  margin: 0;
}

.Card-desc {
  color: #666;
  line-height: 1.5;
  margin: 0;
}

.Card-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: auto;
}

.Card-price {
  font-size: 1.5rem;
  font-weight: 700;
  color: #b36200;
}

.Card-btn {
  background: #222;
  color: white;
  border: none;
  padding: 10px 18px;
  border-radius: 20px;
  cursor: pointer;
}

现在关键来了,我们基于容器宽度来改变卡片内部布局。当容器宽度大于600px时,卡片变成左右两栏,图片占一半,文字占一半。

@container (min-width: 600px) {
  .Card {
    flex-direction: row;
  }

  .Card-thumb {
    flex: 1;
    aspect-ratio: 1 / 1;
  }

  .Card-body {
    flex: 1;
    justify-content: center;
  }

  .Card-desc {
    display: block;
  }
}

如果容器再大一点,比如超过900px,可以在卡片里让副标题变成更明显的样式,甚至改变按钮大小。这种多重阈值可以叠加:

@container (min-width: 900px) {
  .Card-title {
    font-size: 2rem;
  }

  .Card-btn {
    padding: 14px 28px;
    font-size: 1.1rem;
  }
}

这就是真正的组件级响应式。你把这个卡片放到任何宽度的容器里,它都会自动调整自己。以后你不需要再给某个页面单独写“卡片兼容样式”。

容器单位:再也不用calc算宽度了

除了查询尺寸,容器查询还带来了新的单位:cqw(容器宽度的1%)、cqh(容器高度的1%)、cqi(内联轴容器尺寸1%)、cqb(块轴容器尺寸1%)。比如cqmaxcqmin

举例,我希望卡片标题的字号始终是容器宽度的5%,就可以写:

.Card-title {
  font-size: 5cqw;
}

以前用响应式字体要么靠vw,要么用clamp。但vw是相对于视口,在侧边栏里卡片窄,标题却跟着视口变大,非常不合理。用cqw后,字体大小就直接参照父容器了,真正做到“因地制宜”。

我还常用容器单位来设定间距:

.Card-body {
  padding: 4cqw;
}

这样容器越宽,内边距越大,不会出现宽容器里内容贴边的感觉。

样式查询:不只是尺寸,还能查“状态”

注意,@container除了可以查 min-width,还能查样式值。以前我们只能用JS判断某个元素有没有某个类,现在CSS原生提供了样式查询。语法类似:

@container style(--featured: true) {
  .Card {
    border: 2px solid gold;
  }
}

这个功能目前叫“样式容器查询”,支持按自定义属性能不能匹配来切换样式。不过需要容器上显式开启:

.ProductWrapper {
  container-type: inline-size;
  /* 不冲突,它可以同时支持样式查询 */
}

样式查询在日常开发中非常有用。例如,你有一个组件,它有一个data属性表示状态,容器可以设置 --theme: dark,然后内部所有标题颜色都跟着变。现在写法就是:

@container style(--theme: dark) {
  .Card-title {
    color: #e0e0e0;
  }

  .Card-desc {
    color: #aaa;
  }
}

这个特性还比较新,但要提前了解一下它,等到浏览器支持度上来就能直接用。

不会破坏现有@media体系吧?

完全不会,容器查询和@media是互补的。媒体查询关注设备环境(窄屏手机、大屏电视),容器查询关注组件环境(放侧边栏还是主内容区)。正确的策略是:

  • 用@media做页面级布局,比如侧边栏要不要收起来,顶部导航变成汉堡菜单。
  • 用@container做组件内部样式,让组件自己适应所在的区域。

这种分离让CSS模块化更加彻底。你甚至可以做一个完全与视口无关的组件,放在任何页面里都能自适应。对于像产品卡片、表格、富文本区域这种复用性很强的组件,幸福感提升非常明显。

浏览器兼容性和小技巧

截至今天,Chrome/Edge 105+、Safari 16+、Firefox 110+ 对容器查询的基本特性已经支持得不错。移动端主流浏览器也都能用。如果项目需要兼容旧内核,可以考虑给容器查询做渐进增强,比如先写一套默认垂直样式,在@supports (container-type: inline-size)里再写容器查询逻辑。

.Card {
  /* 默认设备老样式 */
  display: block;
}

@supports (container-type: inline-size) {
  .ProductWrapper {
    container-type: inline-size;
  }

  .Card {
    display: flex;
    flex-direction: column;
  }

  @container (min-width: 600px) {
    .Card {
      flex-direction: row;
    }
  }
}

这样不支持的浏览器也能优雅降级,不会白屏。不过现在大部分场景已经可以放心使用。

万一容器查询失效了?先查容器类型

很多时候写了@container没生效,最可能的原因就是祖先元素没有设置container-type。另外如果容器是块级元素,inline-size 只监视宽度。但如果你希望高度变化也能触发查询,就要用size,但是要注意,一旦设置container-type: size,该容器自身的尺寸不能再由内容决定,否则会陷入死循环。所以我一般只用inline-size,少用size

还有,容器不能查询自己本身,只能在子元素(包括后代的任何元素)上写@container。如果你想把卡片本身写进自己的 @container 里,那是无效的。

写在最后

容器查询让组件真正拥有了“自适应”的能力,而不是页面去适应视口。过去无法实现的复杂嵌套响应式,现在用几行CSS就搞定了。我在重构项目里用上它之后,删掉了不少为组件不同使用场景单独写的补丁样式,维护起来轻松多了。

如果你也厌倦了组件在不同父容器里“翻车”,打开Chrome,把container-type: inline-size加上,试着把你的第一个组件变成“会思考的容器”。不用太多,一个卡片就够了。

技术的乐趣就在于这种微小但踏实的改进。

CSS容器查询写起来有多爽?组件化响应式从此不再靠@media
收藏 (0) 打赏

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

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

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

淘吗网 css CSS容器查询写起来有多爽?组件化响应式从此不再靠@media https://www.taomawang.com/web/css/2508.html

常见问题

相关文章

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

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