容器查詢是 CSS 的新功能,可讓您編寫樣式邏輯,以便針對父項元素的功能 (例如寬度或高度) 設定子項樣式。近期,polyfill 的重大更新已發布,同時也已在瀏覽器中提供支援。
在本篇文章中,您將能一窺 polyfill 的運作方式、克服的挑戰,以及使用時的最佳做法,為訪客提供絕佳的使用者體驗。
深入解析
轉譯
當瀏覽器中的 CSS 剖析器遇到不明的 at-rule (例如全新的 @container
規則) 時,會將其捨棄,就好像該規則從未存在一樣。因此,polyfill 必須要做的首要且最重要的事,就是將 @container
查詢轉譯成不會遭到捨棄的內容。
轉譯的第一步是將頂層 @container
規則轉換為 @media 查詢。這可確保內容保持在同一個群組中。例如使用 CSSOM API 和查看 CSS 來源時。
@container (width > 300px) { /* content */ }
@media all { /* content */ }
在容器查詢問世之前,CSS 並未提供作者任意啟用或停用一組規則的方法。如要為這項行為進行半透明轉譯,還必須轉換容器查詢中的規則。每個 @container
都會獲得專屬 ID (例如 123
),用於轉換各個選取器,讓選取器只在元素具有包含此 ID 的 cq-XYZ
屬性時才套用。這個屬性會在執行階段由 polyfill 設定。
@container (width > 300px) { .card { /* ... */ } }
@media all { .card:where([cq-XYZ~="123"]) { /* ... */ } }
請注意,使用 :where(...)
虛擬類別。通常,加入額外的屬性選取器會提高選取器的特異性。使用擬似類別時,您可以套用額外條件,同時保留原始的特定性。如要瞭解這項操作的重要性,請參考以下範例:
@container (width > 300px) {
.card {
color: blue;
}
}
.card {
color: red;
}
根據這段 CSS,具有 .card
類別的元素一律應具有 color: red
,因為後者規則一律會使用相同的選擇器和特定性覆寫前者規則。因此,轉譯第一個規則並加入額外的屬性選取器 (不含 :where(...)
) 會提高特異性,並導致 color: blue
錯誤套用。
不過,:where(...)
擬群組屬性相當新穎。對於不支援 polyfill 的瀏覽器,polyfill 提供了安全又簡單的解決方法:您可以刻意在 @container
規則中手動新增虛擬 :not(.container-query-polyfill)
選取器,藉此提高規則的專屬性:
@container (width > 300px) { .card { color: blue; } } .card { color: red; }
@container (width > 300px) { .card:not(.container-query-polyfill) { color: blue; } } .card { color: red; }
這麼做有許多好處:
- 來源 CSS 中的選取器已變更,因此可以明確看到特異性的差異。這也能做為說明文件,讓您在不再需要支援因應做法或 polyfill 時,知道哪些項目會受到影響。
- 規則的特定性一律相同,因為 polyfill 不會變更。
在轉譯期間,polyfill 會將這個虛設值替換為具有相同特異性的屬性選取器。為避免發生任何意外,polyfill 會同時使用兩種選取器:原始來源選取器用於判斷元素是否應接收 polyfill 屬性,而轉譯的選取器則用於樣式設定。
擬元素
您可能會問:如果 polyfill 在元素上設定部分 cq-XYZ
屬性,以便納入專屬容器 ID 123
,那麼如何支援無法設定屬性的擬造元素?
擬造元素一律會繫結至 DOM 中的實際元素,稱為「原始元素」。在轉譯期間,系統會改為將條件式選取器套用至這個實際元素:
@container (width > 300px) { #foo::before { /* ... */ } }
@media all { #foo:where([cq-XYZ~="123"])::before { /* ... */ } }
條件式選取器不會轉換為 #foo::before:where([cq-XYZ~="123"])
(這會導致無效),而是會移至原始元素 #foo
的結尾。
不過,這並非必要條件。容器不得修改「不在」內部的任何內容 (且容器不能位於自身內部),但如果 #foo
本身就是要查詢的容器元素,就會發生這種情況。#foo[cq-XYZ]
屬性會誤用變更,且任何 #foo
規則都會誤用套用。
為修正這個問題,polyfill 實際上會使用兩個屬性:一個只能由父項套用至元素,另一個則可由元素套用至自身。後者屬性用於指定偽元素的選取器。
@container (width > 300px) { #foo, #foo::before { /* ... */ } }
@media all { #foo:where([cq-XYZ-A~="123"]), #foo:where([cq-XYZ-B~="123"])::before { /* ... */ } }
由於容器絕不會將第一個屬性 (cq-XYZ-A
) 套用至自身,因此只有在不同的父項容器符合容器條件並套用時,第一個選取器才會進行比對。
容器相對單位
容器查詢也提供幾個新的單位,可用於 CSS,例如 cqw
和 cqh
,分別代表最接近的適當上層容器的寬度和高度的 1%。為支援這些功能,系統會使用 CSS 自訂屬性將單位轉換為 calc(...)
運算式。polyfill 會透過容器元素內嵌樣式設定這些屬性的值。
.card { width: 10cqw; height: 10cqh; }
.card { width: calc(10 * --cq-XYZ-cqw); height: calc(10 * --cq-XYZ-cqh); }
還有邏輯單元,例如 cqi
和 cqb
,分別用於內嵌大小和區塊大小。這項作業稍微複雜一些,因為內嵌和區塊軸會由使用單位的元素的 writing-mode
決定,而非要查詢的元素。為支援這項功能,polyfill 會將內嵌樣式套用至 writing-mode
與其父項不同的任何元素。
/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);
/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);
單位現在可以轉換為適當的 CSS 自訂屬性,就像先前一樣。
屬性
容器查詢也新增了幾個 CSS 屬性,例如 container-type
和 container-name
。由於 getComputedStyle(...)
等 API 無法搭配未知或無效的屬性使用,因此這些屬性也會在剖析後轉換為 CSS 自訂屬性。如果無法剖析屬性 (例如因為屬性含有無效或不明的值),則會讓瀏覽器自行處理。
.card { container-name: card-container; container-type: inline-size; }
.card { --cq-XYZ-container-name: card-container; --cq-XYZ-container-type: inline-size; }
這些屬性會在偵測到時轉換,讓 polyfill 能與 @supports
等其他 CSS 功能搭配使用。這項功能是使用 polyfill 的最佳做法基礎,詳情請參閱下文。
@supports (container-type: inline-size) { /* ... */ }
@supports (--cq-XYZ-container-type: inline-size) { /* ... */ }
根據預設,CSS 自訂屬性會沿用,舉例來說,.card
的任何子項都會採用 --cq-XYZ-container-name
和 --cq-XYZ-container-type
的值。這絕對不是原生資源的行為。為解決這個問題,polyfill 會在任何使用者樣式之前插入下列規則,確保每個元素都會收到初始值,除非另有規則刻意覆寫。
* {
--cq-XYZ-container-name: none;
--cq-XYZ-container-type: normal;
}
最佳做法
雖然大多數訪客都會使用內建容器查詢支援功能的瀏覽器,但仍應為其他訪客提供良好的使用體驗。
在初始載入期間,polyfill 必須先完成許多工作,才能為頁面建立版面配置:
- 需要載入並初始化 polyfill。
- 樣式表需要進行剖析和轉譯。由於沒有任何 API 可存取外部樣式表的原始來源,因此可能需要非同步重新擷取,但最好只從瀏覽器快取中擷取。
如果 polyfill 未仔細解決這些問題,可能會導致 Core Web Vitals 退步。
為了讓您更輕鬆地為訪客提供良好體驗,polyfill 的設計會優先處理首次輸入延遲 (FID)和累積式版面配置偏移 (CLS),但可能會犧牲最大內容繪製 (LCP)。具體來說,polyfill 無法保證容器查詢會在首次顯示畫面前評估。也就是說,為了提供最佳使用者體驗,您必須確保任何會因使用容器查詢而受到大小或位置影響的內容,在 polyfill 載入並轉譯 CSS 之前都處於隱藏狀態。其中一種做法就是使用 @supports
規則:
@supports not (container-type: inline-size) {
#content {
visibility: hidden;
}
}
建議您將這項功能與純 CSS 載入動畫結合,並將其絕對定位在 (隱藏) 內容上,以便告知訪客發生了什麼事。如需這項方法的完整示範,請參閱這篇文章。
我們建議採用這種做法,原因如下:
- 純 CSS 載入器可為使用較新版瀏覽器的使用者減少額外負擔,同時為使用舊版瀏覽器和較慢網路的使用者提供輕量化意見回饋。
- 將載入器的絕對定位與
visibility: hidden
結合,即可避免版面配置位移。 - polyfill 載入後,這個
@supports
條件就會停止通過,您的內容就會顯示。 - 在內建容器查詢支援功能的瀏覽器中,條件永遠不會通過,因此頁面會如預期在第一次繪製時顯示。
結論
如果您想在舊版瀏覽器上使用容器查詢,不妨試試polyfill。如果遇到任何問題,歡迎回報問題。
我們迫不及待想看到您運用這項服務打造的驚人作品。