原文:https://ishadeed.com/article/say-hello-to-css-container-queries/
作者:Ahmad Shadeed
译者:Levix
在我過去六年的前端開發生涯裡,我從沒像現在這樣對一個 CSS 功能感到如此興奮。容器查詢功能現在可以在 Chrome Canary 中的 flag(chrome://flags)配置使用,感謝 Miriam Suzanne 以及其他朋友的努力。
我記得看到過很多有關 CSS 容器查詢的調侃,但最終它出現了。在本篇文章中,我將帶你了解為什麼我們需要容器查詢,它如何讓你的生活(CSS 編碼)變得更輕鬆,最重要的是你可以用它實現更強大的組件和佈局。
如果你跟我一樣為之興奮,讓我們開始吧。你準備好了嗎?
CSS 媒體查詢的問題#
一個網頁由不同的內容和組件組成,我們可以使用 CSS 媒體查詢讓它具備響應性,這看起來沒毛病,但它存在一定的局限性。例如,我們可以使用媒體查詢在移動設備以及桌面設備上顯示一個組件的最小化版本。
通常,響應式網頁設計不是基於視口(viewport)或者螢幕尺寸,而是基於容器尺寸,參考下面的例子:
我們有一個非常經典的卡片組件佈局,它存在兩種佈局方式:
- 堆疊的(見上圖左側)
- 橫向的(見上圖主區域)
CSS 有多種方式可以實現上述佈局,但最常見的方式如下代碼所示,我們需要創建一個基礎組件,基於它做一些適配。
.c-article {
/* 默認狀態,堆疊版本 */
}
.c-article > * + * {
margin-top: 1rem;
}
/* 水平版本 */
@media (min-width: 46rem) {
.c-article--horizontal {
display: flex;
flex-wrap: wrap;
}
.c-article > * + * {
margin-top: 0;
}
.c-article__thumb {
margin-right: 1rem;
}
}
注意,我們創建了 .c-article--horizontal
類來處理該組件的水平佈局,當視口寬度大於 46rem 時,則該組件會切換為水平佈局。
這看起來沒什麼問題,但我覺得這樣處理存在其局限性,我希望的組件響應式是基於其父容器寬度,而不是瀏覽器視口或者螢幕尺寸。
考慮到我們希望在主要區域使用了默認的 .c-card
類,將會發生什麼呢?卡片將擴展到其父容器的寬度,因此整體佈局看起來會比較大,見下圖:
我們可以使用 CSS 容器查詢解決這個問題(是的,這是終極方案),在深入研究區它們之前,讓我帶你先了解一下我們想要的結果。
我們需要告訴該組件如果它的直屬父級容器寬度大於 400px,就需要切換為水平樣式,以下是 CSS 代碼配置:
<div class="o-grid">
<div class="o-grid__item">
<article class="c-article">
<!-- 內容 -->
</article>
</div>
<div class="o-grid__item">
<article class="c-article">
<!-- 內容 -->
</article>
</div>
</div>
.o-grid__item {
contain: layout inline-size;
}
.c-article {
/* 默認樣式 */
}
@container (min-width: 400px) {
.c-article {
/* 使文章水平而非卡片式的樣式 */
}
}
CSS 容器查詢將如何幫助我們?#
警告:CSS 容器查詢目前只在 Chrome Canary 瀏覽器的實驗標誌下支持。
通過 CSS 容器查詢,我們可以解決上述問題,並製作一個絲滑的組件。這意味著,我們可以把組件放到一個狹窄的父容器中,它將會變成堆疊的版本,或者放到寬大的父容器中,它會自適應為水平版本,而這些自適應變化都獨立於視口寬度。
以下是我的設想。
紫色的輪廓代表父容器的寬度,注意當它寬度變大時,該組件是如何適配它?這就是 CSS 容器查詢的強大之處。
容器查詢是如何工作的#
我們現在在 Chrome canary 上嘗試使用容器查詢,請到 chrome://flags
回車後搜索 “container queries”,啟用它(譯者注:Chrome 94 版本開啟後也能正常使用)。
第一步是添加 contain
屬性。由於組件將根據它的父容器寬度適配,我們需要告知瀏覽器只對受影響的區域重繪,而不是整個頁面,有了 contain
屬性,我們可以讓瀏覽器提前知道這一點。
inline-size
值意味著只對父容器的寬度變化進行適配,我嘗試使用了 block-size
,但發現它無法滿足需求,如果我存在錯誤請糾正我。
<div class="o-grid">
<div class="o-grid__item">
<article class="c-article">
<!-- 內容 -->
</article>
</div>
<div class="o-grid__item">
<article class="c-article">
<!-- 內容 -->
</article>
</div>
<!-- 其他文章.. -->
</div>
.o-grid__item {
contain: layout inline-size;
}
這是第一步,我們定義了 .o-grid__item
元素作為 .c-article
的父容器。
下一步我們添加需要的樣式,讓容器查詢功能生效。
.o-grid__item {
contain: layout inline-size;
}
@container (min-width: 400px) {
.c-article {
display: flex;
flex-wrap: wrap;
}
/* 其他 CSS.. */
}
@container
指向的是 .o-grid__item
元素,其中 min-width: 400px
是它的寬度(適配),我們甚至可以更進一步添加更多的樣式。下面的視頻介紹了卡片組件可以實現的功能(需要梯子):
我們在視頻中的樣式配置有:
- 默認情況下,是卡片式的外觀。
- 一張帶有小縮略圖的橫向卡片。
- 一張帶有大縮略圖的橫向卡片。
- 如果父容器太大,將顯示一個主圖的樣式,以表明它是一篇有特色的文章。
讓我們來探討一下 CSS 容器查詢的使用案例。
CSS 容器查詢的使用案例#
容器查詢和 CSS Grid auto-fit
#
在某些情況下,在 CSS grid 佈局中使用 auto-fit
會出現意想不到的結果,例如,組件太寬,其內容難以閱讀。
為了更直觀了解問題現象,這裡有一個視覺效果,它展示了 CSS grid 佈局中 auto-fit
和 auto-fill
的區別。
注意,當使用 auto-fit
時,子項會自適應以覆蓋可用空間,然而,在 auto-fill
例子裡, 網格子項不會延伸,我們將會有一個自由空間(最右邊虛線部分)。
你現在可能會很困惑,這跟 CSS 容器查詢有什麼關聯?每個網格子項都是一個容器,當它展開時(也就是我們使用的 auto-fit
),組件需要在此基礎上改變。
<div class="o-grid">
<div class="o-grid__item">
<article class="c-article"></article>
</div>
<div class="o-grid__item">
<article class="c-article"></article>
</div>
<div class="o-grid__item">
<article class="c-article"></article>
</div>
<div class="o-grid__item">
<article class="c-article"></article>
</div>
</div>
.o-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 1rem;
}
當我們有 4 個元素,大致看起來像下面那樣。
當文章數量減少時,此時會發生變化,下圖展示發生了什麼。我們的文章越少,它們就會變得越寬。原因是使用了 auto-fit
,第一個看起來還不錯,但最後兩個(每行 2 個,每行 1 個)看起來就不怎麼樣了,因為它們太寬了。
如果每個文章組件都根據父容器寬度改變佈局呢?這樣的話,我們就可以充分享受 auto-fit
帶來的好處。以下是我們需要做的:
如果網格子項的寬度大於 400px 時,那麼文章應該切換到水平風格。
我們可以這樣做:
.o-grid__item {
contain: layout inline-size;
}
@container (min-width: 400px) {
.c-article {
display: flex;
flex-wrap: wrap;
}
}
另外,如果文章是網格中唯一一項,我們想用主圖來展示。
.o-grid__item {
contain: layout inline-size;
}
@container (min-width: 700px) {
.c-article {
display: flex;
justify-content: center;
align-items: center;
min-height: 350px;
}
.card__thumb {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
以上,我們有了一个自適應其父級寬度的組件,而且它可以在任何環境下工作,這不是很好嗎?
查看 CodePen 上的演示。
側邊欄和主體#
通常,我們需要調整適配一個組件,能夠讓它在寬度較小的容器中展示,如 <aside>
。
一個經典案例是通訊部分,當它寬度較小時,我們需要將其堆疊展示,而當寬度足夠時,我們需要它能夠水平展開。
正如你上圖所看到的,我們有一個適配兩種不同場景下的時事通訊組件:
- 側邊欄部分
- 主要區域
如果沒有容器查詢,估計不太可能實現,除非我們在 CSS 中增加一個變體類,例如, .newsletter--stacked
之類的。
我知道我們可以強制包裹這些子項,防止彈性佈局沒有足夠的空間,但還是不夠的。我需要更多的掌控力來做以下事情:
- 隱藏特定元素。
- 按鈕撐滿容器。
.newsletter-wrapper {
contain: layout inline-size;
}
/* 默認樣式, 堆疊版本 */
.newsletter {
/* CSS 樣式 */
}
.newsletter__title {
font-size: 1rem;
}
.newsletter__desc {
display: none;
}
/* 水平版本 */
@container (min-width: 600px) {
.newsletter {
display: flex;
justify-content: space-between;
align-items: center;
}
.newsletter__title {
font-size: 1.5rem;
}
.newsletter__desc {
display: block;
}
}
這裡有個視頻展示。
查看 CodePen 上的演示。
分頁#
我發現分頁場景非常適合使用容器查詢,最初,我們可以有 “上一頁” 和 “下一頁” 按鈕,我們可以隱藏它們,並在有足夠空間的情況下顯示全部分頁。
參考下圖。
要處理上述圖中狀態,我們需要首先處理默認樣式 (堆疊按鈕),接著處理其餘兩種狀態。
.wrapper {
contain: layout inline-size;
}
@container (min-width: 250px) {
.pagination {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.pagination li:not(:last-child) {
margin-bottom: 0;
}
}
@container (min-width: 500px) {
.pagination {
justify-content: center;
}
.pagination__item:not(.btn) {
display: block;
}
.pagination__item.btn {
display: none;
}
}
查看 CodePen 上的演示。
個人資料卡片#
這是另一個適合在多個場景中使用的案例,小狀態適用於小的視口大小以及像側邊欄那樣的場景,而更大的狀態可以在更大的環境下工作,比如把它放在 2-col 網格中。
.p-card-wrapper {
contain: layout inline-size;
}
.p-card {
/* 默認樣式 */
}
@container (min-width: 450px) {
.meta {
display: flex;
justify-content: center;
gap: 2rem;
border-top: 1px solid #e8e8e8;
background-color: #f9f9f9;
padding: 1.5rem 1rem;
margin: 1rem -1rem -1rem;
}
/* 其他樣式 */
}
這樣,我們可以看到在不使用單一媒體查詢的情況下,component words 在不同場景下是如何使用的。
查看 CodePen 上的演示。
表單元素#
我還沒有深入研究表單的案例,但我想到的是將標籤從水平狀態切換到堆疊狀態。
.form-item {
contain: layout inline-size;
}
.input-group {
@container (min-width: 350px) {
display: flex;
align-items: center;
gap: 1.5rem;
input {
flex: 1;
}
}
}
在下面的演示中自己嘗試一下。在 CodePen 上查看演示。
測試組件#
現在我們已經嘗試了在幾個案例中使用 CSS 容器查詢,那我們如何測試組件(快速驗證)?值得慶幸的是,我們可以通過組件的父容器 CSS resize
屬性來做到這一點。
.parent {
contain: layout inline-size;
resize: horizontal;
overflow: auto;
}
我從 Bramus Van Damme 的文章中學到了這個技巧。
在 DevTools 中調試容器查詢容易嗎?#
暫時不能,你無法看到像 @container (min-width: value)
這樣的東西,但我認為這只是時間問題,最終會被支持。
是否有可能提供備選方案?#
是的!當然可以,在某些方面提供備選方案是可能的,這裡有兩篇很棒的文章解釋了如何做到這一點:
- Container Query Solutions with CSS Grid and Flexbox 作者 Stephanie Eckles
- Container Queries are actually coming 作者 Andy Bell
總結#
我很喜歡學習 CSS 容器查詢並在瀏覽器中使用它。我知道它還沒有被官方支持,但現在是在瀏覽器中使用它的好時機。
作為前端開發人員,我們工作的一部分就是給那些致力於實現這些新功能的人提供測試和幫助,我們測試的越多,一旦所有主流瀏覽器都支持該功能,我們看到的問題就越少。
感謝您的閱讀。