CSS Container Queries容器查询实战:组件级响应式布局新方案

媒体查询(Media Queries)基于视口宽度决定布局,组件样式与所在页面强耦合。同一个卡片组件放在侧边栏和主内容区需要不同布局,媒体查询无法区分这种场景。CSS Container Queries 让组件根据父容器宽度自适应,实现真正的组件级响应式设计。2023 年起主流浏览器全面支持,本文从语法到工程实践给出完整方案。

Container Queries与Media Queries的本质区别

媒体查询的响应条件是浏览器视口尺寸,所有组件共享同一套断点。当页面布局复杂时,侧边栏中的组件在宽屏下实际可用宽度可能只有 300px,却因视口宽度 1440px 而触发了”桌面”布局,导致内容溢出或留白。

容器查询将响应条件从视口转移到最近的祖先容器。组件根据自身实际可用空间决定布局,与页面整体宽度解耦。一个按钮组在 400px 容器中横向排列,在 200px 容器中自动切换为纵向堆叠,无需 JavaScript 介入。

container-type与容器上下文声明

使用容器查询前,需要将元素声明为查询容器:

/* 声明容器上下文 */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.main-content {
  container-type: inline-size;
  container-name: main;
}

/* 简写形式 */
.card-wrapper {
  container: card / inline-size;
}

container-type: inline-size 表示以容器的内联尺寸(通常是宽度)作为查询维度。inline-size 是最常用值,因为大部分响应式布局关注宽度变化。size 同时查询宽高,但会建立尺寸包含(size containment),要求容器有明确尺寸,使用场景较少。

container-name 为容器命名,当组件被嵌套在多层容器中时,可通过名称指定查询哪个容器的尺寸。

@container查询语法与条件匹配

.card {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

/* 当容器宽度 >= 400px 时切换为双列布局 */
@container (min-width: 400px) {
  .card {
    grid-template-columns: 200px 1fr;
    gap: 24px;
  }
}

/* 当容器宽度 >= 600px 时切换为三列布局 */
@container (min-width: 600px) {
  .card {
    grid-template-columns: 200px 1fr 1fr;
  }
}

/* 命名容器查询 */
@container main (min-width: 800px) {
  .card {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

/* 范围查询语法(CSS Level 4) */
@container (400px <= width <= 600px) {
  .card {
    background: yellow;
  }
}

容器查询支持 min-widthmax-width 及范围语法。命名容器查询通过 @container main (min-width: 800px) 形式指定目标容器,适用于多个嵌套容器场景。

组件化实战:自适应卡片组件

一个完整的响应式卡片组件,根据容器宽度切换布局并调整字体大小:

<!-- HTML 结构 -->
<div class="page-layout">
  <aside class="sidebar">
    <article class="product-card">
      <img class="product-card__image" src="product.jpg" alt="产品图" />
      <div class="product-card__body">
        <h3 class="product-card__title">无线降噪耳机</h3>
        <p class="product-card__desc">主动降噪,40小时续航</p>
        <div class="product-card__footer">
          <span class="product-card__price">¥899</span>
          <button class="product-card__btn">加入购物车</button>
        </div>
      </div>
    </article>
  </aside>

  <main class="main-content">
    <article class="product-card">
      <!-- 同一组件结构 -->
    </article>
  </main>
</div>
/* CSS */
.page-layout {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 32px;
}

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

.main-content {
  container-type: inline-size;
}

/* 基础布局:窄容器下的纵向排列 */
.product-card {
  display: flex;
  flex-direction: column;
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.product-card__image {
  width: 100%;
  height: 180px;
  object-fit: cover;
}

.product-card__body {
  padding: 16px;
}

.product-card__title {
  font-size: 16px;
  margin: 0 0 8px;
}

.product-card__desc {
  font-size: 14px;
  color: #666;
  margin: 0 0 12px;
}

.product-card__footer {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* 容器宽度 >= 300px:图片变水平排列 */
@container (min-width: 300px) {
  .product-card {
    flex-direction: row;
  }

  .product-card__image {
    width: 140px;
    height: auto;
    min-height: 100%;
  }

  .product-card__body {
    flex: 1;
  }

  .product-card__footer {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

/* 容器宽度 >= 500px:加大间距和字体 */
@container (min-width: 500px) {
  .product-card__image {
    width: 200px;
  }

  .product-card__body {
    padding: 24px;
  }

  .product-card__title {
    font-size: 20px;
  }

  .product-card__desc {
    font-size: 15px;
  }
}

同一个 .product-card 组件在侧边栏(容器宽 280px)中显示为纵向卡片布局,在主内容区(容器宽 800px+)中自动切换为横向图文布局,无需为不同页面位置编写不同 CSS。

容器查询单位:cqw/cqh/cqi/cqb

容器查询单位类似于视口单位 vw/vh,但参照物是容器尺寸而非视口:

.hero-section {
  container-type: inline-size;
}

.hero-title {
  /* 10cqi = 容器内联尺寸的 10% */
  font-size: 10cqi;
  /* 最小不小于 1.5rem,最大不超过 4rem */
  font-size: clamp(1.5rem, 10cqi, 4rem);
}

.hero-padding {
  /* 5cqw = 容器宽度的 5% */
  padding: 5cqw;
}

单位含义:cqw 容器宽度百分比,cqh 容器高度百分比,cqi 容器内联尺寸百分比,cqb 容器块尺寸百分比,cqmin/cqmax 取较小/较大值。结合 clamp() 实现流体排版,标题大小随容器宽度平滑变化且有上下限保护。

容器查询的浏览器兼容与降级方案

Container Queries 在 Chrome 105+、Safari 16+、Firefox 110+ 全面支持,覆盖 2026 年主流浏览器。针对旧浏览器的渐进增强方案:

@supports (container-type: inline-size) {
  /* 容器查询支持的样式 */
  @container (min-width: 400px) {
    .product-card {
      flex-direction: row;
    }
  }
}

@supports not (container-type: inline-size) {
  /* 降级到媒体查询 */
  @media (min-width: 768px) {
    .sidebar .product-card {
      flex-direction: column;
    }
    .main-content .product-card {
      flex-direction: row;
    }
  }
}

也可使用 polyfill 方案如 container-query-polyfill 通过 ResizeObserver 实现降级支持,但 polyfill 有性能开销且不支持容器查询单位。推荐策略是对核心布局使用 @supports 降级,对增强效果仅在支持的浏览器中启用。

Tailwind CSS 3.4+ 内置容器查询支持,通过 @container 变体直接使用:

<div class="@container">
  <div class="flex flex-col @md:flex-row @lg:gap-8">
    <!-- 组件内容 -->
  </div>
</div>

@container 标记容器上下文,@md:@lg: 前缀对应不同容器宽度断点,与 md:lg: 视口断点语法一致,降低心智成本。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/csscontainerqueries-rong-qi-cha-xun-shi-zhan-zu-jian-ji/

(0)
小编小编
上一篇 1天前
下一篇 1天前

相关推荐