充分发挥 CSS 容器查询的强大功能:Netflix 团队的经验教训

Jeremy Weeks
Jeremy Weeks
Stefan Heymanns
Stefan Heymanns

容器查询彻底改变了开发者对响应式设计的处理方式,Netflix 团队亲身体验了容器查询对简化开发、提高灵活性和提升性能的深远影响。本文详细介绍了使用容器查询的关键优势,并将其与旧方法(尤其是依赖 JavaScript 进行布局控制的方法)进行了比较。其中包含代码示例来阐明每个要点,展示容器查询如何让您作为开发者的工作变得更加轻松。

1. 简化了组件设计,“自下而上”与“自上而下”

Netflix 团队经历的重大转变之一,就是从“自上而下”的设计方法转变为“自下而上”的方法。在容器查询之前,父容器必须完全了解其子容器的布局要求。使用容器查询时,此逻辑会相反,允许子组件根据自己的容器大小控制其布局。这简化了父级的角色,并减少了代码中的布局逻辑量。

示例:容器查询与媒体查询和 JavaScript

之前(需要 JavaScript)

/* Layout with media queries */
.card {
    width: 100%;
}

@media (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@media (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}
// JavaScript to detect parent container size
const container = document.querySelector('.container');
const card = document.querySelector('.card');

function adjustLayout() {
    if (window.innerWidth >= 900) {
        card.style.width = '33.33%';
    } else if (window.innerWidth >= 600) {
        card.style.width = '50%';
    } else {
        card.style.width = '100%';
    }
}

window.addEventListener('resize', adjustLayout);
adjustLayout();

之后:

/* Container Query */
.container {
    container-type: inline-size;
}

.card {
    width: 100%;
}

@container (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@container (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}

此示例展示了父容器如何不再需要管理子布局。@container 规则可让 .card 对其直接容器的大小做出响应,从而简化布局逻辑并完全消除对 JavaScript 的需求。

2. 无需复杂的媒体查询即可实现自适应

Netflix 团队发现了容器查询如何简化响应能力,尤其是对于移动优先设计。您可以创建可重复使用的组件,这些组件会根据其容器的尺寸进行调整,从而实现各种屏幕尺寸和设备上的动态布局,而无需编写复杂的媒体查询。对于 Netflix 等移动流量占主导地位的应用,这尤其有用。

示例:使用容器查询实现组件响应能力

之前:

/* Desktop versus Mobile
this only works if.sidebar is directly contained by a viewport-width element */
.sidebar {
    width: 300px;
}

@media (max-width: 768px) {
    .sidebar {
        width: 100%;
    }
}

之后:

/* Responsive sidebar based on container,
.sidebar can be placed in any element of any width */
.container {
    container-type: inline-size;
}

.sidebar {
    width: 100%;
}

@container (min-width: 768px) {
    .sidebar {
        width: 300px;
    }
}

.sidebar 现在会响应容器大小,而不是依赖于基于视口的媒体查询,从而能够更自然地适应动态布局,而无需知道视口或父容器的大小。

3. 减少了对 JavaScript 的布局管理依赖

在容器查询之前,包括 Netflix 在内的许多团队都不得不依赖 JavaScript 来实现动态布局。通过查询窗口大小,JavaScript 会触发布局更改,从而增加复杂性和潜在的 bug 风险。容器查询通过允许 CSS 根据容器大小处理布局自适应性,消除了这一需求。

示例:移除基于 JavaScript 的布局逻辑

之前:

const cardContainer = document.querySelector('.card-container');
const cards = cardContainer.children;

function adjustLayout() {
    if (cardContainer.offsetWidth > 900) {
        cards.forEach(card => card.style.width = '33.33%');
    } else if (cardContainer.offsetWidth > 600) {
        cards.forEach(card => card.style.width = '50%');
    } else {
        cards.forEach(card => card.style.width = '100%');
    }
}

window.addEventListener('resize', adjustLayout);
adjustLayout();

之后:

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

.card {
    width: 100%;
}

@container (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@container (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}

这种方法不仅减少了所需的 JavaScript 量,还通过避免运行时计算来提升性能。

4. 代码更少,错误更少

Netflix 团队发现,使用容器查询可以减少代码行数,并减少与布局相关的 bug。通过将布局逻辑从 JavaScript 移至 CSS 并消除对复杂媒体查询的需求,开发者可以编写更易于维护的代码。

示例:减少布局代码

Netflix 团队发现,采用容器查询后,CSS 代码显著减少,某些组件的 CSS 代码减少了高达 30%。与此同时,该团队通过剥离控制子组件的逻辑,简化了许多复杂且有时容易发生冲突的媒体查询,从而实现了更高程度的关注分离。这样不仅可以加快开发速度,还可以最大限度地减少潜在故障点,从而减少 bug。

之前:

/* Before with complex media queries */
.card {
    width: 100%;
}

@media (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@media (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}

之后

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

.card {
    width: 100%;
}

@container (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@container (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}

5. 改进开发者体验

bq。“这让生活变得更轻松了”

容器查询最被低估的优势之一可能是改进了开发者体验。通过让 CSS 以更直观、以组件为中心的方式运行,开发者可以专注于构建可重复使用且灵活的组件,而无需担心这些组件在每种可能的布局场景中的行为方式。

正如 Netflix 团队的一位成员所说:“CSS 从一开始就应该是这样运作的。”

6. polyfill 回退

虽然所有主流浏览器现在都支持容器查询,但仍有用户使用较低版本的浏览器。回退至关重要,Netflix 团队使用了 Web 社区贡献者创建的此 JavaScript polyfill。借助特征检测,实现起来非常简单:

if (! CSS.supports("container-type:size")) {
  /*use polyfill from
  https://www.npmjs.com/package/container-query-polyfill */
 }

总结

容器查询是 CSS 领域的一大进步,让开发者能够更轻松地构建可在网站的不同部分重复使用的灵活自适应组件。通过减少对 JavaScript 布局的依赖、消除复杂的媒体查询并加快开发速度,它们在性能和可维护性方面具有显著优势。目前,大多数用例都位于 Netflix 的 Tudum 页面上,我们可能会计划在 Netflix 的其他部分使用容器查询。Netflix 团队认为容器查询是开发者工具箱中的重要工具,随着越来越多的开发者认识到容器查询带来的灵活性和强大功能,容器查询的使用范围只会不断扩大。无论是改造现有组件还是设计全新组件,容器查询都为实现响应式设计提供了更简单、更清晰的途径。

如果您尚未尝试容器查询,不妨试一试。您可能会发现,容器查询会以意想不到的方式简化您的工作流。