容器查詢是新的 CSS 功能,可讓您編寫樣式邏輯指定父項元素的地圖項目 (例如寬度或高度),藉此設定子項的樣式。我們最近發布了 polyfill 的重大更新,與瀏覽器支援到達網頁一樣。
本文將帶您一窺 Polyfill 的運作方式、克服的挑戰,以及運用 Polyfill 為網站提供良好使用者體驗的最佳做法。
深入解析
變頻
當瀏覽器內的 CSS 剖析器遇到不明的規則 (例如全新的 @container
規則) 時,就會將其捨棄,就像在不存在時一樣。因此,Polyfill 的第一要務是將 @container
查詢轉譯為不會捨棄的項目。
轉碼的第一步是將頂層 @container
規則轉換為 @media 查詢。這樣可以確保內容歸為一組。例如使用 CSSOM API 和查看 CSS 來源時。
@container (width > 300px) { /* content */ }
@media all { /* content */ }
在導入容器查詢之前,CSS 無法任意啟用或停用規則群組。如要實現這個行為,您還需要轉換容器查詢內的規則。每個 @container
都有專屬 ID (例如 123
),用於轉換每個選取器,讓只有元素具備 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 提供了安全簡便的解決方法:您可以在 @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 在元素上設定了包含專屬容器 ID 123
的 cq-XYZ
屬性,那麼系統如何支援對這些元素上未設定屬性的虛擬元素?
虛擬元素一律會與 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 自訂屬性。
屬性
容器查詢也會新增 container-type
和 container-name
等 CSS 屬性。由於 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ădureserve 的 Unsplash 提供。