← 中文资源

简体中文版本

CSS 容器查询实现自适应卡片

让商品卡片按自身容器宽度而非视口切换单列与双列,使用命名容器、cqw 单位和兼容回退。

阅读英文原文

功能说明

卡片外层 .card-shell 声明 container-type: inline-size 与 container-name: card,内部 .card 默认纵向排列图片与文字。当容器宽度达到 30rem 时切换为两列,图片列使用 minmax 限制最小宽度;达到 48rem 时进一步调整比例。标题字号用 clamp 配合 2cqw,使文字随容器宽度平滑变化。不支持容器查询的浏览器退回基于视口宽度的媒体查询。

关键代码

container-type: inline-size 建立行内尺寸查询上下文,同时带来布局、样式和尺寸包含,子元素不能反过来撑大容器。@container card (min-width: 30rem) 查询的是最近的同名容器,而不是视口。grid-template-columns 使用 minmax(12rem, 2fr) 3fr,保证图片不会缩到不可读。cqw 是容器宽度的百分比单位,仅在查询容器后代中有效。@supports not (container-type: inline-size) 提供旧浏览器回退。

使用注意

容器不能查询自身,查询规则必须作用于容器的后代。给容器设置固定高度会与尺寸包含冲突,应让内容决定高度。卡片放在侧栏、主栏或弹窗中时会自动适配,这是相对媒体查询的优势。命名容器可以嵌套,但同名时以最近祖先为准。回退方案仍需在真实旧浏览器中检查,因为 @supports 的解析行为可能因版本而异。

验证方式

在浏览器开发者工具中拖动放置卡片的侧栏宽度,视口保持不变时卡片应在 30rem 与 48rem 处切换布局,标题字号也随之变化。把卡片移入更宽的弹窗,确认断点跟随容器而非窗口。禁用容器查询支持或使用旧浏览器,检查媒体查询回退是否仍能给出可用的两列布局。

代码示例

/* Markup contract:
   <article class="card-shell"><div class="card">
     <img class="card__media" src="/desk.jpg" alt="Standing desk">
     <div class="card__body">
       <h2 class="card__title">Standing desk</h2>
       <p>Height-adjustable frame with a memory preset.</p>
     </div>
   </div></article> */

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

.card {
  display: grid;
  gap: var(--space-3, 1rem);
  padding: var(--space-4, 1.5rem);
  border: 1px solid var(--color-border, #d4d4d8);
  border-radius: var(--radius-lg, 0.75rem);
  background: var(--color-surface, #ffffff);
}

.card__media {
  inline-size: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: calc(var(--radius-lg, 0.75rem) - 0.25rem);
}

.card__title {
  margin-block: 0;
  font-size: 1.15rem;
  text-wrap: balance;
}

/* The query measures .card-shell, not the viewport. */
@container card (min-width: 30rem) {
  .card {
    grid-template-columns: minmax(12rem, 2fr) 3fr;
    align-items: start;
  }

  .card__title {
    font-size: clamp(1.15rem, 1rem + 2cqw, 1.6rem);
  }
}

@container card (min-width: 48rem) {
  .card {
    grid-template-columns: minmax(14rem, 1fr) 2fr;
  }
}

/* Viewport fallback for browsers without container query support. */
@supports not (container-type: inline-size) {
  @media (min-width: 48rem) {
    .card {
      grid-template-columns: minmax(14rem, 1fr) 2fr;
      align-items: start;
    }
  }
}