CSS 容器查詢:用例和遷移策略
已發表: 2022-03-10當我們為 UI 元素編寫媒體查詢時,我們總是根據屏幕尺寸描述該元素的樣式。 當目標元素媒體查詢的響應性應僅取決於視口大小時,此方法效果很好。 讓我們看一下下面的響應式頁面佈局示例。
然而,響應式 Web 設計 (RWD) 並不局限於頁面佈局——單個 UI 組件通常具有可以根據視口尺寸改變其樣式的媒體查詢。
您可能已經註意到前面陳述的一個問題——單個 UI 組件佈局通常並不完全依賴於視口尺寸。 雖然頁面佈局是與視口尺寸密切相關的元素,並且是 HTML 中最頂層的元素之一,但 UI 組件可以在不同的上下文和容器中使用。 如果你仔細想想,視口只是一個容器,UI 組件可以嵌套在其他容器中,其樣式會影響組件的尺寸和佈局。
即使在頂部和底部都使用相同的產品卡片組件,組件樣式不僅取決於視口尺寸,還取決於上下文和放置它的容器 CSS 屬性(如示例中的網格)。
當然,我們可以構建我們的 CSS,以便我們支持不同上下文和容器的樣式變化,以手動解決佈局問題。 在最壞的情況下,這種變體將添加樣式覆蓋,這將導致代碼重複和特異性問題。
.product-card { /* Default card style */ } .product-card--narrow { /* Style variation for narrow viewport and containers */ } @media screen and (min-width: 569px) { .product-card--wide { /* Style variation for wider viewport and containers */ } }
但是,這更多是針對媒體查詢限制的解決方法,而不是適當的解決方案。 在為 UI 元素編寫媒體查詢時,當目標元素具有佈局不會中斷的最小尺寸時,我們試圖為斷點找到一個“神奇的”視口值。 簡而言之,我們將“神奇的”視口尺寸值鏈接到元素尺寸值。 該值通常與視口尺寸不同,並且在內部容器尺寸或佈局更改時容易出現錯誤。
下面的示例展示了這個確切的問題——即使響應式產品卡片元素已經實現並且在標準用例中看起來不錯,但如果將其移動到具有影響元素尺寸的 CSS 屬性的不同容器中,它看起來會損壞。 每個額外的用例都需要添加額外的 CSS 代碼,這可能導致重複代碼、代碼膨脹和難以維護的代碼。
這是容器查詢試圖解決的問題之一。 容器查詢通過依賴於目標元素尺寸的查詢擴展了現有的媒體查詢功能。 使用這種方法有三個主要好處:
- 容器查詢樣式的應用取決於目標元素本身的尺寸。 UI 組件將能夠適應任何給定的上下文或容器。
- 開發人員不需要在特定容器或特定上下文中尋找將視口媒體查詢鏈接到 UI 組件的目標維度的“幻數”視口維度值。
- 無需為不同的上下文和用例添加額外的 CSS 類或媒體查詢。
“理想的響應式網站是一個靈活的模塊化組件系統,可以重新調整用途以在多種情況下提供服務。”
——“容器查詢:再次違反”,Mat Marquis
在深入研究容器查詢之前,我們需要檢查瀏覽器支持並了解如何在瀏覽器中啟用實驗性功能。
瀏覽器支持
容器查詢是一項實驗性功能,在撰寫本文時目前在 Chrome Canary 版本中可用。 如果您想繼續並運行本文中的 CodePen 示例,您需要在以下設置 URL 中啟用容器查詢。
chrome://flags/#enable-container-queries
如果您使用的瀏覽器不支持容器查詢,將在 CodePen 演示旁邊提供展示預期工作示例的圖像。
使用容器查詢
容器查詢不像常規媒體查詢那麼簡單。 我們必須在我們的 UI 元素中添加一行額外的 CSS 代碼才能使容器查詢正常工作,但這是有原因的,我們將在接下來介紹這一點。
收容屬性
CSS contain
屬性已添加到大多數現代瀏覽器中,並且在撰寫本文時已獲得 75% 的瀏覽器支持。 contain
屬性主要用於性能優化,通過向瀏覽器提示頁面的哪些部分(子樹)可以被視為獨立的並且不會影響對樹中其他元素的更改。 這樣,如果單個元素發生更改,瀏覽器將僅重新呈現該部分(子樹)而不是整個頁面。 使用 contains 屬性值,我們可以指定要使用的contain
類型—— layout
、 size
或paint
。
contain
許多關於 contains 屬性的精彩文章更詳細地概述了可用選項和用例,因此我將只關注與容器查詢相關的屬性。
用於優化的 CSS contentment 屬性與容器查詢有什麼關係? 為了使容器查詢起作用,瀏覽器需要知道元素的子佈局中是否發生更改,它應該只重新渲染該組件。 當組件被渲染或組件的尺寸發生變化時,瀏覽器將知道將容器查詢中的代碼應用到匹配的組件。
我們將使用layout
style
作為contain
,但我們還需要一個附加值來向瀏覽器發出關於發生更改的軸的信號。
-
inline-size
內聯軸上的遏制。 預計這個值會有更多的用例,所以它首先被實施。 -
block-size
塊軸上的遏制。 它仍在開發中,目前不可用。
contain
屬性的一個小缺點是我們的佈局元素需要是contain
元素的子元素,這意味著我們正在添加一個額外的嵌套級別。
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
.card { contain: layout inline-size style; } .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ }
請注意,我們如何不將此值添加到更遠的類似父級的section
,並使容器盡可能靠近受影響的元素。
“績效是避免工作並使您所做的任何工作盡可能高效的藝術。 在許多情況下,它是關於使用瀏覽器,而不是反對它。”
——“渲染性能”,保羅·劉易斯
這就是為什麼我們應該正確地向瀏覽器發出有關更改的信號。 使用 contains 屬性包裝遠距離父元素contain
會適得其反,並對頁面性能產生負面影響。 在濫用contain
屬性的最壞情況下,佈局甚至可能中斷,瀏覽器將無法正確呈現它。
容器查詢
在將contain
屬性添加到卡片元素包裝器後,我們可以編寫容器查詢。 我們已經為具有card
類的元素添加了一個contain
屬性,因此現在我們可以在容器查詢中包含它的任何子元素。
就像常規媒體查詢一樣,我們需要使用min-width
或max-width
屬性定義一個查詢,並將所有選擇器嵌套在塊內。 但是,我們將使用@container
關鍵字而不是@media
來定義容器查詢。
@container (min-width: 568px) { .card__wrapper { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { min-width: auto; height: auto; } }
card__wrapper
和card__image
元素都是card
元素的子元素,它定義了contain
屬性。 當我們用容器查詢替換常規媒體查詢,移除窄容器的額外 CSS 類,並在支持容器查詢的瀏覽器中運行 CodePen 示例時,我們得到以下結果。
請注意,容器查詢目前不顯示在 Chrome 開發者工具中,這使得調試容器查詢有點困難。 預計將來會在瀏覽器中添加適當的調試支持。
您可以看到容器查詢如何使我們能夠創建更健壯且可重用的 UI 組件,這些組件幾乎可以適應任何容器和佈局。 但是,該功能還需要對容器查詢進行適當的瀏覽器支持。 讓我們嘗試看看我們是否可以使用漸進增強來實現容器查詢。
漸進式增強和 Polyfills
讓我們看看是否可以為 CSS 類變體和媒體查詢添加回退。 我們可以使用帶有@supports
規則的 CSS 功能查詢來檢測可用的瀏覽器功能。 但是,我們無法檢查其他查詢,因此我們需要添加檢查contain: layout inline-size style
值。 我們必須假設支持inline-size
屬性的瀏覽器也支持容器查詢。
/* Check if the inline-size value is supported */ @supports (contain: inline-size) { .card { contain: layout inline-size style; } } /* If the inline-size value is not supported, use media query fallback */ @supports not (contain: inline-size) { @media (min-width: 568px) { /* ... */ } } /* Browser ignores @container if it's not supported */ @container (min-width: 568px) { /* Container query styles */ }
但是,這種方法可能會導致樣式重複,因為容器查詢和媒體查詢都應用了相同的樣式。 如果您決定使用漸進增強來實現容器查詢,您可能希望使用 CSS 預處理器(如 SASS)或後處理器(如 PostCSS)來避免重複代碼塊,並改用 CSS mixins 或其他方法。
由於此容器查詢規範仍處於實驗階段,因此請務必記住,規範或實現在未來版本中可能會發生變化。
或者,您可以使用 polyfill 來提供可靠的回退。 我想強調兩個 JavaScript polyfill,它們目前似乎得到了積極維護,並提供了必要的容器查詢功能:
-
cqfill
的cqfill
用於 CSS 和 PostCSS 的 JavaScript polyfill - Chris Garcia
react-container-query
React 的自定義鉤子和組件
從媒體查詢遷移到容器查詢
如果您決定在使用媒體查詢的現有項目上實現容器查詢,則需要重構 HTML 和 CSS 代碼。 我發現這是添加容器查詢的最快和最直接的方法,同時為媒體查詢提供可靠的回退。 讓我們看一下前面的卡片示例。
<section> <div class="card__wrapper card__wrapper--wide"> <!-- Wide card content --> </div> </section> /* ... */ <aside> <div class="card__wrapper"> <!-- Narrow card content --> </div> </aside>
.card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ } .card__image { /* ... */ } @media screen and (min-width: 568px) { .card__wrapper--wide { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { /* ... */ } }
首先,將應用了媒體查詢的根 HTML 元素包裝為具有contain
屬性的元素。
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
@supports (contain: inline-size) { .card { contain: layout inline-size style; } }
接下來,將媒體查詢包裝在功能查詢中並添加容器查詢。
@supports not (contain: inline-size) { @media (min-width: 568px) { .card__wrapper--wide { /* ... */ } .card__image { /* ... */ } } } @container (min-width: 568px) { .card__wrapper { /* Same code as .card__wrapper--wide in media query */ } .card__image { /* Same code as .card__image in media query */ } }
雖然這種方法會導致一些代碼膨脹和重複代碼,但通過使用 SASS 或 PostCSS 可以避免重複開發代碼,因此 CSS 源代碼保持可維護性。
一旦容器查詢獲得適當的瀏覽器支持,您可能需要考慮刪除@supports not (contain: inline-size)
代碼塊並繼續專門支持容器查詢。
Stephanie Eckles 最近發表了一篇關於容器查詢的精彩文章,涵蓋了各種遷移策略。 我建議查看它以獲取有關該主題的更多信息。
用例場景
正如我們從前面的示例中看到的那樣,容器查詢最適合用於高度可重用的組件,其佈局取決於可用的容器空間,並且可以在各種上下文中使用並添加到頁面上的不同容器中。
其他示例包括(示例需要支持容器查詢的瀏覽器):
- 模塊化組件,如卡片、表單元素、橫幅等。
- 適應性佈局
- 具有不同功能的移動和桌面分頁
- 有趣的 CSS 調整大小實驗
結論
一旦規範在瀏覽器中實施並得到廣泛支持,容器查詢可能會成為改變遊戲規則的功能。 它將允許開發人員在組件級別編寫查詢,將查詢移近相關組件,而不是使用遙遠且幾乎不相關的視口媒體查詢。 這將導致更健壯、可重用和可維護的組件能夠適應各種用例、佈局和容器。
就目前而言,容器查詢仍處於早期的實驗階段,實施很容易發生變化。 如果您現在想在項目中開始使用容器查詢,則需要使用帶有特徵檢測的漸進增強或使用 JavaScript polyfill 來添加它們。 這兩種情況都會在代碼中產生一些開銷,因此如果您決定在這個早期階段使用容器查詢,請確保計劃在該功能得到廣泛支持後重構代碼。
參考
- David A. Herron 的“容器查詢:快速入門指南”
- “向 CSS 容器查詢問好”,Ahmad Shadeed
- “Chrome 52 中的 CSS 包含”,Paul Lewis
- “使用 CSS 包含屬性幫助瀏覽器優化”,Rachel Andrew