容器查询 @container 实战:告别无休止的媒体查询

问题场景

你有没有遇到过这种情况:写了一个精美的卡片组件,在首页大屏上完美展示,结果放到侧边栏小区域里时,布局全崩了?

css 复制代码
/* 传统媒体查询------只认视口,不认容器 */
.card {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

/* ❌ 当卡片放在 300px 宽的侧边栏时,两列依然生效 */
/* 只能通过加类名或写一堆 hack 来处理 */
@media (max-width: 768px) {
  .card {
    grid-template-columns: 1fr; /* 但这是看视口宽度的!不是容器宽度!*/
  }
}

传统 @media 查询只关心浏览器视口宽度。当你的组件被塞进侧边栏、弹窗、折叠面板里时,视口可能还是 1920px,但组件实际可用宽度只剩 400px------这个时候布局怎么调?加 class?用 resize observer JS 算?又脏又累。

容器查询(Container Queries) 正是为了解决这个问题而生------让组件根据自身容器的宽度来决定布局。

原因分析

组件级的响应式 vs 页面级的响应式

@media 是为页面布局设计的,它回答的是"视口多宽?"。而现代前端组件化开发中,一个组件可能出现在页面的任何位置------主内容区 1200px,侧边栏 320px,Modal 里 600px,Split Panel 左侧 400px。

如果你的组件写死了 @media (max-width: 768px) 来切换布局,那它在侧边栏里永远都是"小屏模式",即使视口有 4K 宽。👇

javascript 复制代码
// ❌ 传统方案的狼狈
function SidebarCard() {
  // 还得额外监测容器宽度,自己算
  return <div className="sidebar-card card--compact">...</div>
}

关键是 containment

CSS Container Queries 的核心是 containment(包含性) 。你通过 container-type 告诉浏览器:"这个元素是一个查询容器,它的子元素可以查我的尺寸。" 浏览器因此知道它需要为这个容器维护独立的尺寸信息,这就是 contain 属性的延伸。

解决方案

基础用法三步走

第一步:声明容器

在你想查询的父元素上设置容器:

css 复制代码
.card-container {
  container-type: inline-size;
  /* 或者简写:container: inline-size; */
  /* 也可以起个名字,方便多个容器区分 */
  container-name: sidebar;
}

第二步:写容器查询

css 复制代码
/* 默认:大容器布局------两列 */
.card {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  padding: 20px;
  background: #fff;
  border-radius: 12px;
}

/* 当容器宽度 < 500px 时------变为单列 */
@container (max-width: 500px) {
  .card {
    grid-template-columns: 1fr;
  }
  .card__image {
    width: 100%;
    height: 200px;
  }
}

/* 当容器宽度 < 300px 时------极致紧凑模式 */
@container (max-width: 300px) {
  .card {
    grid-template-columns: 1fr;
    gap: 8px;
    padding: 12px;
  }
  .card__title {
    font-size: 14px;
  }
  .card__desc {
    display: none; /* 空间太小,隐藏描述 */
  }
}

第三步:用上就完事

html 复制代码
<!-- 同一组件,不同容器,自动适配 -->
<div class="main-content">
  <div class="card-container">
    <div class="card">
      <div class="card__image"><img src="..." alt="" /></div>
      <div class="card__body">
        <h3 class="card__title">标题</h3>
        <p class="card__desc">描述信息...</p>
      </div>
    </div>
  </div>
</div>

<aside class="sidebar">
  <div class="card-container">
    <div class="card">
      <!-- 同样的结构,自动触发紧凑模式 -->
    </div>
  </div>
</aside>

实操:一个完整的响应式商品卡片

来看一个真实场景------商品卡片组件,它既能放在首页网格(宽),也能放在推荐侧边栏(窄),还能在弹窗里展示:

html 复制代码
<div class="product-grid">
  <div class="product-wrapper">
    <div class="product-card">
      <img class="product-card__img" src="product.jpg" alt="商品" />
      <div class="product-card__info">
        <h4 class="product-card__name">智能手表 Pro Max</h4>
        <p class="product-card__price">¥2,999</p>
        <p class="product-card__rating">★★★★☆ 4.5</p>
        <button class="product-card__btn">加入购物车</button>
      </div>
    </div>
  </div>
</div>
css 复制代码
/* 声明容器 */
.product-wrapper {
  container-type: inline-size;
  container-name: product;
}

/* 默认布局:水平排列(宽容器) */
.product-card {
  display: flex;
  gap: 16px;
  padding: 16px;
  border: 1px solid #eee;
  border-radius: 12px;
  background: #fff;
  transition: all 0.2s;
}

.product-card__img {
  width: 160px;
  height: 160px;
  object-fit: cover;
  border-radius: 8px;
  flex-shrink: 0;
}

/* 容器宽度 ≤ 480px → 改为垂直排列 */
@container product (max-width: 480px) {
  .product-card {
    flex-direction: column;
  }
  .product-card__img {
    width: 100%;
    height: 200px;
  }
}

/* 容器宽度 ≤ 300px → 极简模式 */
@container product (max-width: 300px) {
  .product-card {
    flex-direction: column;
    padding: 10px;
  }
  .product-card__rating {
    display: none;
  }
  .product-card__img {
    height: 140px;
  }
  .product-card__btn {
    width: 100%;
    font-size: 13px;
    padding: 8px;
  }
}

在 React / Vue 中的最佳实践

React 示例:

tsx 复制代码
// ProductCard.tsx
import './ProductCard.css'

// 容器包裹层负责声明 container-type
function ProductWrapper({ children }: { children: React.ReactNode }) {
  return <div className="product-wrapper">{children}</div>
}

// 卡片组件只管写 @container,完全无感知
function ProductCard({ product }: { product: Product }) {
  return (
    <div className="product-card">
      <img className="product-card__img" src={product.image} alt="" />
      <div className="product-card__info">
        <h4>{product.name}</h4>
        <p className="product-card__price">{product.price}</p>
        <button>加入购物车</button>
      </div>
    </div>
  )
}

// 使用:任何地方用 ProductWrapper 包裹即可自适应
function HomePage() {
  return (
    <div className="home-layout">
      {/* 主区域:宽容器 */}
      <section className="main-grid">
        {products.map(p => (
          <ProductWrapper key={p.id}>
            <ProductCard product={p} />
          </ProductWrapper>
        ))}
      </section>

      {/* 侧边栏:窄容器,自动触发紧凑布局 */}
      <aside className="sidebar">
        <h3>推荐商品</h3>
        {hotProducts.map(p => (
          <ProductWrapper key={p.id}>
            <ProductCard product={p} />
          </ProductWrapper>
        ))}
      </aside>
    </div>
  )
}

Vue 示例:

vue 复制代码
<!-- ProductContainer.vue -->
<template>
  <div class="product-container">
    <slot />
  </div>
</template>

<style scoped>
.product-container {
  container-type: inline-size;
}
</style>

<!-- 使用 -->
<ProductContainer>
  <ProductCard :product="item" />
</ProductContainer>

浏览器兼容性与降级

截至 2026 年 7 月,Container Queries 的基础功能已得到所有现代浏览器支持(Chrome 105+、Firefox 110+、Safari 16+)。如果你需要兼容老浏览器,用 polyfill:

bash 复制代码
npm install container-query-polyfill

然后在入口文件引入:

typescript 复制代码
// 只在需要时加载 polyfill
import 'container-query-polyfill'

推荐用 渐进增强 策略:先写基于 @container 的现代化布局,降级方案用 flex/grid 的自然折行行为兜底:

css 复制代码
.product-card {
  /* ✅ 降级:flex-wrap 保证至少不崩 */
  display: flex;
  flex-wrap: wrap;
}

/* ✅ 增强:支持容器查询的浏览器获得更精确控制 */
@container (min-width: 500px) {
  .product-card {
    flex-wrap: nowrap;
  }
}

要点总结

对比 @media 媒体查询 @container 容器查询
查询基准 浏览器视口 父容器尺寸
适用场景 页面级布局 组件级布局
组件复用性 ❌ 依赖上下文 ✅ 完全自治
维护成本 越写越乱 组件自包含
  1. 父元素声明 :用 container-type: inline-sizecontainer: <name> / inline-size 声明容器
  2. 子元素查询 :用 @container (条件) 代替 @media 查询容器宽度
  3. 命名容器 :多个容器用 container-name 区分,查询时 @container <name> (条件)
  4. 渐进增强:用 flex/grid 折行做兜底,支持容器查询的浏览器获得更优体验
  5. 性能友好container-type 触发 CSS containment,浏览器会优化重排计算
相关推荐
稚南城才子,乌衣巷风流16 小时前
函数:编程中的核心概念
开发语言·前端·javascript
IT_陈寒16 小时前
为什么我的JavaScript异步代码总是不按顺序执行?
前端·人工智能·后端
索西引擎17 小时前
【React】useState 状态更新机制:批量更新策略与“异步“错觉的深层解析
前端·react.js·前端框架
慢功夫17 小时前
💡第五篇:VSCode插件是如何与主进程通信的?
前端·visual studio code
锋行天下17 小时前
打造企业内部知识库系统RAG全栈项目
前端·后端·架构
竹林81817 小时前
用 ethers.js 连接 MetaMask 实现钱包登录:一个真实项目中的完整踩坑记录
前端·javascript
虚惊一场18 小时前
在浏览器里跑 Prettier:格式化 Markdown 的四个难点
前端·javascript·vue.js
前端炒粉18 小时前
手撕小汇总
java·前端·javascript
r_oo_ki_e_18 小时前
vue快速入门
前端·vue.js