容器查詢是全新的 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
屬性時,才會套用此 ID。在執行階段,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 會提供安全簡單的解決方法:你可以刻意手動將虛擬的 :not(.container-query-polyfill)
選取器新增到 @container
規則中,以增加規則的明確性:
@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
末端,而非轉換成 #foo::before:where([cq-XYZ~="123"])
(這樣會無效)。
不過,這並不是一切必要的。容器不得修改其「內部」內的內容 (且容器不能本身是內側),但這其實是當 #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 無法與不明或無效屬性搭配使用,因此這些 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 未謹慎解決這些問題,可能導致網站體驗核心指標降低。
為方便您為訪客提供愉快的使用體驗, Polyfill 設計是優先處理首次輸入延遲時間 (FID) 和累計版面配置位移 (CLS),但可能犧牲最大內容繪製 (LCP)。簡而言之,polyfill 無法保證系統在第一次繪製前會評估容器查詢。因此,為了提供最佳使用者體驗,您「必須」確保在 polyfill 載入並轉譯 CSS 之前,使用容器查詢會影響任何大小或位置的內容。其中一個方法是使用 @supports
規則:
@supports not (container-type: inline-size) {
#content {
visibility: hidden;
}
}
建議你在這類元素中加入單純的 CSS 載入動畫,絕對放置在 (隱藏) 內容上方,讓訪客知道發生了什麼事。這類做法的完整示範請見這裡。
我們建議您採用這種方法,原因如下:
- 對新版瀏覽器的使用者來說,單純的 CSS 載入器不但能減輕負擔,同時也能為使用舊版瀏覽器和較慢網路的使用者提供輕微回饋。
- 藉由結合載入器的絕對定位與
visibility: hidden
,可避免版面配置位移。 - 在 Polyfill 載入後,這個
@supports
條件會停止傳遞,而您的內容將顯示。 - 在內建容器查詢支援的瀏覽器中,這項條件永遠不會通過,因此網頁會在首次顯示時正常顯示。
結論
如果您想在舊版瀏覽器上使用容器查詢,請試試 polyfill。如果遇到任何問題,歡迎回報問題。
我們迫不及待想看到並體驗您用它打造的精彩作品。
特別銘謝
主頁橫幅由 Dan Cristian Păduregam 提供的 Unsplash 影片。