容器查询启用基于可用空间的元素样式。它们使我们能够构建可适应无限、未知布局安排的弹性组件。这与视口媒体查询形成对比,视口媒体查询需要在页面级别协调样式更改。
容器查询与视口媒体查询 {#container-queries-vs-viewport-media-queries}
我们很可能熟悉响应视口的响应式设计和布局,如下图所示。
在视口响应式设计中,将布局网格耦合到断点很流行。这些断点通常与简化的设备尺寸有关,如上图所示,例如手机、平板电脑和台式机。
重要的是,这些断点不一定考虑屏幕上的单个元素和组件,更侧重于组件如何流入预定义的网格。有时,较大的组件(如导航)会与网格分开变形,但通常它们会使用全局断点。
让我们将视口响应式设计与容器响应式设计进行对比。
下图是卡片组件的变体。这三种变体是使用完全独立于视口的容器查询呈现的。卡片样式根据可用空间进行调整。
注意:自 Firefox 110 发布以来,所有常青浏览器都支持容器查询。为了扩展对旧浏览器的支持,可以使用 polyfill。
首先,让我们学习创建容器查询的语法。
定义容器查询 {#defining-container-queries}
第一步是使用属性指定元素是容器container-type
。最基本且目前支持最好的值是inline-size
,在水平书写模式下它等于元素的宽度。所以这个定义意味着我们打算支持基于.container
元素的行内大小的查询:
.container {
container-type: inline-size;
}
向元素添加 acontainer-type
正式将其指定为容器。
接下来,我们将使用容器 at 规则创建实际的容器查询,如果我们曾经分配过媒体查询,它会接受一个看起来很熟悉的参数。
下面的@container
规则说,当 an<h2>
在一个40ch
宽或更大的容器内时,它的颜色应该是蓝色:
@container (min-width: 40ch) {
h2 {
color: blue;
}
}
注意:我们放置在容器查询中的规则不会影响容器本身的样式,只会影响其子项。这意味着我们无法从容器自身的查询中设置容器的样式。但是,如果容器的祖先也被定义为容器,我们可以使用容器查询来设置容器的样式。
为了适应不仅仅是水平书写模式,我们可以更新我们的查询以使用 的逻辑语法inline-size
,而不是严格基于容器的"宽度"查询:
@container (inline-size > 40ch) {
h2 {
color: blue;
}
}
还有更多选项inline-size
,包括block-size
和aspect-ratio
。要了解有关可用大小容器查询以及如何使用它们的更多信息,请查看官方规范。
升级卡片组件 {#upgrading-a-card-component}
如果我们想构建一个没有容器查询的卡片组件,我们可以通过修饰类创建变体。对于卡片大小的变化,这些修饰符可以与断点相关联。这意味着,如果卡片有修饰符,则当视口宽度落在该断点内时,它会被允许更改。
下图显示了三种卡片大小变化及其各自的修改器类别,其中顶部.card
将被视为"默认"。
现在让我们换个角度思考如何使用容器查询来处理这些卡片变体。
我们仍会将顶部卡片设为默认卡片,这实际上意味着它将应用于最窄的宽度。这也将是不支持容器查询的回退版本------在容器查询达到支持成熟度之前需要考虑的一个重要场景。
我们将为中型卡片(水平方向)设置布局,以便在容器宽度为350px
或更大时激活。
最后,当容器的宽度为600px
或更大时,我们将卡片布局设置为使用其图像作为背景。
这将创建一个可根据卡片容器的大小进行调整的卡片元素。试玩以下CodePen 演示以了解实际效果。(请注意右下角的"调整我的大小!"手柄。)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.card {
--img-ratio: 2/1;
display: grid;
gap: 1rem;
padding: 1rem;
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.35);
border-radius: 0.5rem;
}
.card > img {
height: max(18vh, 12rem);
object-fit: cover;
width: 100%;
}
@supports (aspect-ratio: 1) {
.card > img {
aspect-ratio: var(--img-ratio);
height: auto;
}
}
.card__content > :not(h2, h3, h4) {
font-size: 0.9rem;
}
.card__content {
display: grid;
gap: 0.5rem;
gap: 1cqb;
}
.card-list {
align-items: start;
}
.card-list li {
container-type: inline-size;
}
@container (min-width: 350px) {
.card {
--img-ratio: 1;
grid-auto-flow: column;
align-items: center;
}
}
@container (min-width: 600px) {
.card {
--img-ratio: 2/1;
grid-template-areas: "card";
place-items: center;
padding: 0;
overflow: hidden;
}
.card > * {
grid-area: card;
}
.card__content {
text-align: center;
color: #fff;
padding: 1rem;
}
.card__content {
font-size: 2rem;
}
}
</style>
</head>
<body>
<aside role="note">
<p>Your browser doesn't natively support container queries, but the demo works thanks to the polyfill! Refer to the JS tab for how to include it.</p>
</aside>
<ul role="list" class="flexbox-grid card-list container resize">
<li>
<div class="card">
<img src="https://assets.codepen.io/1101822/jellyfish.jpg" alt="Pink and purple small flourescent jellyfish with white spotted caps float in inky darkness." width="400" height="300">
<div class="card__content">
<h3>Dolor sit amet</h3>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</div>
</div>
</li>
<li>
<div class="card">
<img src="https://assets.codepen.io/1101822/jellyfish.jpg" alt="Pink and purple small flourescent jellyfish with white spotted caps float in inky darkness." width="400" height="300">
<div class="card__content">
<h3>Dolor sit amet</h3>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</div>
</div>
</li>
<li>
<div class="card">
<img src="https://assets.codepen.io/1101822/jellyfish.jpg" alt="Pink and purple small flourescent jellyfish with white spotted caps float in inky darkness." width="400" height="300">
<div class="card__content">
<h3>Dolor sit amet</h3>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</div>
</div>
</li>
</ul>
<script>
// Support Test
const supportsContainerQueries = "container" in document.documentElement.style;
// Conditional Import
if (!supportsContainerQueries) {
import("https://cdn.skypack.dev/container-query-polyfill");
}
</script>
</body>
</html>