CSS Container Queries 实战:告别媒体查询的束缚
CSS Container Queries 实战:告别媒体查询的束缚
CSS 是流动的韵律,JS 是叙事的节奏。
一、从媒体查询到容器查询:思维模式的转变
上周在做一个 dashboard 项目时,我遇到了一个典型难题:同样的卡片组件,在侧边栏和主内容区需要不同的样式。以前我会用媒体查询加一堆条件判断,或者干脆写两套组件。但这次,我决定试试 CSS Container Queries。
容器查询让我们不再依赖视口(viewport)尺寸,而是基于父容器的尺寸来调整样式。这就好比一个变色龙不是根据太阳位置变色,而是根据它趴着的树叶颜色变化——更智能、更精准。
/* 传统方式:媒体查询 */ @media (max-width: 768px) { .card { grid-template-columns: 1fr; } } /* 容器查询方式:基于容器尺寸 */ .card-container { container-type: inline-size; container-name: card; } @container card (max-width: 400px) { .card { grid-template-columns: 1fr; } } @container card (min-width: 401px) and (max-width: 800px) { .card { grid-template-columns: 1fr 1fr; } } @container card (min-width: 801px) { .card { grid-template-columns: 1fr 1fr 1fr; } }二、创建容器上下文
容器查询的第一步是定义容器上下文。通过container-type属性,我们可以告诉浏览器哪些元素需要被当作查询容器。
/* 定义容器上下文 */ .sidebar { container-type: inline-size; container-name: sidebar; } .main-content { container-type: inline-size; container-name: main; } /* 简写形式 */ .dashboard { container: dashboard / inline-size; }container-type有三个可选值:
normal:默认值,不作为容器inline-size:基于内联方向尺寸查询,最常用size:基于双向尺寸查询,会创建新的包含块
三、容器查询长度单位
容器查询还带来了一套全新的长度单位,让子元素可以基于容器尺寸进行自适应:
.card { /* 基于容器宽度的百分比 */ padding: 1cqi; margin: 1cqw; /* 基于容器高度的百分比 */ min-height: 10cqh; /* 基于容器最小/最大尺寸的百分比 */ font-size: clamp(0.875rem, 2cqmin, 1.25rem); max-width: 50cqmax; } @container (min-width: 600px) { .card-title { font-size: 5cqi; /* 容器宽度的 5% */ } .card-body { font-size: 3cqi; line-height: 1.6; } }容器查询单位让组件的内部间距、字体大小、边距等完全跟随容器尺寸变化,真正做到组件级别的完全封装。
四、实战:自适应卡片组件
来看看一个完整的实战案例——一个可以放在任何位置的卡片组件:
<div class="grid-layout"> <div class="card-container"> <div class="card"> <img class="card-image" src="photo.jpg" alt="风景"> <div class="card-content"> <h2 class="card-title">探索自然之美</h2> <p class="card-description">穿越山川湖海,感受大自然的鬼斧神工。</p> <button class="card-btn">了解更多</button> </div> </div> </div> </div>.grid-layout { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem; } .card-container { container: card / inline-size; } .card { display: flex; flex-direction: column; border-radius: 0.5rem; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .card-image { width: 100%; height: auto; object-fit: cover; } .card-content { padding: 1.5rem; } @container card (min-width: 350px) { .card { flex-direction: row; } .card-image { width: 40%; height: 100%; } .card-content { flex: 1; } } @container card (min-width: 600px) { .card { flex-direction: row; } .card-image { width: 200px; height: 200px; border-radius: 50%; margin: 1.5rem; } .card-content { display: flex; flex-direction: column; justify-content: center; } }同一个卡片组件,放在窄容器里是纵向布局,放到宽容器里变成横向布局,再宽一些甚至变成圆头像展示——完全不需要写媒体查询,也不需要 JavaScript 干预。
五、与 Grid 和 Flexbox 的协同
容器查询和 Grid、Flexbox 是天生一对。Grid 负责宏观布局,容器查询负责微观自适应:
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem; container: dashboard / inline-size; } .widget { container: widget / inline-size; background: white; border-radius: 8px; padding: 1rem; } @container widget (max-width: 200px) { .widget-header { flex-direction: column; text-align: center; } .widget-value { font-size: 1.5rem; } .widget-chart { display: none; } } @container widget (min-width: 201px) and (max-width: 400px) { .widget-header { flex-direction: row; align-items: center; gap: 0.5rem; } .widget-chart { height: 100px; } }六、浏览器支持与渐进增强
虽然容器查询已经得到主流浏览器的支持,但在实际项目中,渐进增强仍然是明智的选择:
/* 回退方案:媒体查询 */ .card { display: grid; grid-template-columns: 1fr; } @media (min-width: 768px) { .card { grid-template-columns: 1fr 1fr; } } /* 增强方案:容器查询 */ @supports (container-type: inline-size) { .card-wrapper { container: card / inline-size; } @container card (min-width: 400px) { .card { grid-template-columns: 1fr 1fr; } } @container card (min-width: 700px) { .card { grid-template-columns: 1fr 1fr 1fr; } } }使用@supports进行特性检测,在不支持容器查询的浏览器中优雅降级。这样既利用了新特性的强大能力,又保证了兼容性。
graph TD A[CSS样式层] --> B[变量定义] A --> C[布局系统] A --> D[动画效果] B --> E[主题色彩] B --> F[间距系统] C --> G[Flexbox] C --> H[Grid]七、结语:组件化思维的新高度
容器查询的出现,让 CSS 组件化向前迈进了一大步。我们终于可以写出真正的"一次编写,随处运行"的组件样式——不管它被放在侧边栏、主内容区还是弹窗里,都能自动适配。
回想以前写组件库时,每个组件都要预设使用场景,写一堆使用文档告诉别人"这个组件需要至少 400px 宽度"。现在好了,组件自己会判断、自适应,就像一个有生命的个体。
