CSS 自定義屬性的策略指南
已發表: 2022-03-10所有現代瀏覽器現在都支持 CSS 自定義屬性(有時稱為“CSS 變量”),人們開始在生產中使用它們。 這很好,但它們與預處理器中的變量不同,而且我已經看到很多人使用它們的例子而不考慮它們提供的優勢。
自定義屬性具有巨大的潛力,可以改變我們編寫和構建 CSS 的方式,並在較小程度上改變我們使用 JavaScript 與 UI 組件交互的方式。 我不會關注語法及其工作方式(為此我建議您閱讀“是時候開始使用自定義屬性了”)。 相反,我想更深入地了解充分利用 CSS 自定義屬性的策略。
它們與預處理器中的變量有何相似之處?
自定義屬性有點像預處理器中的變量,但有一些重要的區別。 第一個也是最明顯的區別是語法。
在SCSS
中,我們使用美元符號來表示變量:
$smashing-red: #d33a2c;
在 Less 中,我們使用@
符號:
@smashing-red: #d33a2c;
自定義屬性遵循類似的約定並使用--
前綴:
:root { --smashing-red: #d33a2c; } .smashing-text { color: var(--smashing-red); }
自定義屬性和預處理器中的變量之間的一個重要區別是自定義屬性具有不同的語法來分配值和檢索該值。 在檢索自定義屬性的值時,我們使用var()
函數。
下一個最明顯的區別是名稱。 它們被稱為“自定義屬性”,因為它們確實是 CSS 屬性。 在預處理器中,您幾乎可以在任何地方聲明和使用變量,包括外部聲明塊、媒體規則中,甚至作為選擇器的一部分。
$breakpoint: 800px; $smashing-red: #d33a2c; $smashing-things: ".smashing-text, .cats"; @media screen and (min-width: $breakpoint) { #{$smashing-things} { color: $smashing-red; } }
使用自定義屬性時,上面的大多數示例都是無效的。
自定義屬性對於它們可以用作普通 CSS 屬性的位置具有相同的規則。 將它們視為動態屬性要比變量好得多。 這意味著它們只能在聲明塊內使用,或者換句話說,自定義屬性與選擇器相關聯。 這可以是:root
選擇器,也可以是任何其他有效的選擇器。
:root { --smashing-red: #d33a2c; } @media screen and (min-width: 800px) { .smashing-text, .cats { --margin-left: 1em; } }
您可以在任何您會在屬性聲明中使用值的地方檢索自定義屬性的值。 這意味著它們可以用作單個值,作為速記語句的一部分,甚至可以在calc()
方程中使用。
.smashing-text, .cats { color: var(--smashing-red); margin: 0 var(--margin-horizontal); padding: calc(var(--margin-horizontal) / 2) }
但是,它們不能用於媒體查詢或包括:nth-child()
在內的選擇器。
關於語法和自定義屬性如何工作,您可能還想了解更多信息,例如如何使用回退值以及是否可以將變量分配給其他變量(是的),但是這個基本介紹應該足以理解其餘部分本文中的概念。 有關自定義屬性如何工作的詳細信息,您可以閱讀 Serg Hospodarets 撰寫的“是時候開始使用自定義屬性”了。
動態與靜態
拋開外觀差異不談,預處理器中的變量和自定義屬性之間最顯著的區別在於它們的作用域。 我們可以將變量稱為靜態或動態範圍。 預處理器中的變量是靜態的,而自定義屬性是動態的。
就 CSS 而言,靜態意味著您可以在編譯過程的不同點更新變量的值,但這不能更改之前代碼的值。
$background: blue; .blue { background: $background; } $background: red; .red { background: $background; }
結果是:
.blue { background: blue; } .red { background: red; }
一旦將其呈現給 CSS,變量就消失了。 這意味著我們可以潛在地讀取.scss
文件並確定其輸出,而無需了解任何有關 HTML、瀏覽器或其他輸入的信息。 自定義屬性不是這種情況。
預處理器確實有一種“塊作用域”,其中變量可以在選擇器、函數或混合中臨時更改。 這會更改塊內變量的值,但它仍然是靜態的。 這與塊相關,而不是選擇器。 在下面的示例中,變量$background
在.example
塊內發生了更改。 即使我們使用相同的選擇器,它也會變回塊外的初始值。
$background: red; .example { $background: blue; background: $background; } .example { background: $background; }
這將導致:
.example { background: blue; } .example { background: red; }
自定義屬性的工作方式不同。 就自定義屬性而言,動態範圍意味著它們受繼承和級聯的影響。 該屬性與選擇器相關聯,如果值更改,則會影響所有匹配的 DOM 元素,就像任何其他 CSS 屬性一樣。
這很棒,因為您可以在媒體查詢中更改自定義屬性的值,使用諸如懸停之類的偽選擇器,甚至使用 JavaScript。
a { --link-color: black; } a:hover, a:focus { --link-color: tomato; } @media screen and (min-width: 600px) { a { --link-color: blue; } } a { color: var(--link-color); }
我們不必更改自定義屬性的使用位置——我們使用 CSS 更改自定義屬性的值。 這意味著使用相同的自定義屬性,我們可以在同一頁面的不同位置或上下文中具有不同的值。
全球與本地
除了靜態或動態之外,變量也可以是全局的或局部的。 如果您編寫 JavaScript,您將對此很熟悉。 變量既可以應用於應用程序中的所有內容,也可以將其範圍限制為特定的函數或代碼塊。
CSS 類似。 我們有一些在全球範圍內應用的東西和一些更本地化的東西。 品牌顏色、垂直間距和排版都是您可能希望在您的網站或應用程序中全局一致地應用的示例。 我們也有當地的東西。 例如,一個按鈕組件可能有一個小變體和一個大變體。 您不希望將這些按鈕的大小應用於所有輸入元素甚至頁面上的每個元素。
這是我們在 CSS 中熟悉的東西。 我們開發了設計系統、命名約定和 JavaScript 庫,所有這些都有助於隔離本地組件和全局設計元素。 自定義屬性為處理這個老問題提供了新的選擇。
默認情況下,CSS 自定義屬性的範圍僅限於我們應用它們的特定選擇器。 所以它們有點像局部變量。 但是,自定義屬性也是繼承的,因此在許多情況下它們的行為類似於全局變量——尤其是在應用於:root
選擇器時。 這意味著我們需要考慮如何使用它們。
如此多的示例顯示將自定義屬性應用於:root
元素,儘管這對於演示來說很好,但它可能導致混亂的全局範圍和意外的繼承問題。 幸運的是,我們已經吸取了這些教訓。
全局變量往往是靜態的
有一些小的例外,但一般來說,CSS 中的大多數全局事物也是靜態的。
品牌顏色、排版和間距等全局變量往往不會從一個組件到另一個組件發生太大變化。 當他們確實發生變化時,這往往是全球品牌重塑或其他一些在成熟產品上很少發生的重大變化。 將這些東西作為變量仍然是有意義的,它們在許多地方使用,並且變量有助於保持一致性。 但它們是動態的是沒有意義的。 這些變量的值不會以任何動態方式改變。
出於這個原因,我強烈建議對全局(靜態)變量使用預處理器。 這不僅確保它們始終是靜態的,而且在代碼中直觀地表示它們。 這可以使 CSS 更具可讀性和更易於維護。
局部靜態變量沒問題(有時)
您可能會認為,鑑於全局變量是靜態的,通過反射,所有局部變量可能都需要是動態的。 雖然局部變量確實往往是動態的,但這遠沒有全局變量是靜態的趨勢那麼強烈。
在許多情況下,局部靜態變量是完全可以的。 我在組件文件中使用預處理器變量主要是為了方便開發人員。
考慮具有多種尺寸變化的按鈕組件的經典示例。
我的scss
可能看起來像這樣:
$button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { // Visual styles } .btn-sml { font-size: $button-sml; } .btn-med { font-size: $button-med; } .btn-lrg { font-size: $button-lrg; }
顯然,如果我多次使用變量或從大小變量派生邊距和填充值,這個例子會更有意義。 然而,快速製作不同尺寸原型的能力可能是一個充分的理由。
因為大多數靜態變量都是全局變量,所以我喜歡區分僅在組件內部使用的靜態變量。 為此,您可以為這些變量添加組件名稱的前綴,或者您可以使用其他前綴,例如組件的c-variable-name
或本地l-variable-name
。 您可以使用任何您想要的前綴,也可以為全局變量添加前綴。 無論您選擇什麼,區分都很有幫助,尤其是在將現有代碼庫轉換為使用自定義屬性時。
何時使用自定義屬性
如果可以在組件內部使用靜態變量,我們什麼時候應該使用自定義屬性? 將現有的預處理器變量轉換為自定義屬性通常沒有什麼意義。 畢竟,自定義屬性的原因是完全不同的。 當我們的 CSS 屬性相對於 DOM 中的條件發生變化時,自定義屬性才有意義——尤其是動態條件,例如:focus
、 :hover
、媒體查詢或 JavaScript。
我懷疑我們將始終使用某種形式的靜態變量,儘管將來我們可能需要更少,因為自定義屬性提供了組織邏輯和代碼的新方法。 在那之前,我認為在大多數情況下,我們將使用預處理器變量和自定義屬性的組合。
知道我們可以將靜態變量分配給自定義屬性會很有幫助。 無論它們是全局的還是本地的,在許多情況下將靜態變量轉換為本地動態自定義屬性都是有意義的。
注意:您知道$var
是自定義屬性的有效值嗎? 最新版本的 Sass 認識到這一點,因此我們需要插入分配給自定義屬性的變量,例如: #{$var}
。 這告訴 Sass 你想要輸出變量的值,而不僅僅是樣式表中的$var
。 這僅在自定義屬性等情況下才需要,其中變量名也可以是有效的 CSS。
如果我們採用上面的按鈕示例並決定所有按鈕都應該使用移動設備上的小變化,而不管 HTML 中應用的類是什麼,現在這是一個更加動態的情況。 為此,我們應該使用自定義屬性。
$button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { --button-size: #{$button-sml}; } @media screen and (min-width: 600px) { .btn-med { --button-size: #{$button-med}; } .btn-lrg { --button-size: #{$button-lrg}; } } .btn { font-size: var(--button-size); }
在這裡,我創建了一個自定義屬性: --button-size
。 此自定義屬性最初的作用域是使用btn
類的所有按鈕元素。 然後,我將btn-med
和btn-lrg
類的--button-size
值更改為 600px 以上。 最後,我將這個自定義屬性應用到一個地方的所有按鈕元素。
不要太聰明
自定義屬性的動態特性允許我們創建一些巧妙而復雜的組件。
隨著預處理器的引入,我們中的許多人使用 mixin 和自定義函數創建了具有巧妙抽象的庫。 在有限的情況下,像這樣的例子在今天仍然有用,但在大多數情況下,我使用預處理器的時間越長,我使用的功能就越少。 今天,我幾乎只對靜態變量使用預處理器。
自定義屬性不會(也不應該)不受此類實驗的影響,我期待看到許多聰明的例子。 但從長遠來看,可讀和可維護的代碼總是會戰勝聰明的抽象(至少在生產中)。
我最近在 Free Code Camp Medium 上閱讀了一篇關於此主題的優秀文章。 它由 Bill Sourour 編寫,名為“Don't Do It At Runtime。 在設計時做。” 我不會解釋他的論點,而是讓你閱讀它。
預處理器變量和自定義屬性之間的一個關鍵區別是自定義屬性在運行時起作用。 這意味著就複雜性而言,使用預處理器可能已經可以接受的邊界對於自定義屬性可能不是一個好主意。
最近為我說明了這一點的一個例子是:
:root { --font-scale: 1.2; --font-size-1: calc(var(--font-scale) * var(--font-size-2)); --font-size-2: calc(var(--font-scale) * var(--font-size-3)); --font-size-3: calc(var(--font-scale) * var(--font-size-4)); --font-size-4: 1rem; }
這會產生一個模塊化的規模。 模塊化比例是一系列使用比率相互關聯的數字。 它們通常用於網頁設計和開發來設置字體大小或間距。
在此示例中,每個自定義屬性都是使用calc()
確定的,方法是獲取前一個自定義屬性的值並將其乘以比率。 這樣做,我們可以得到規模中的下一個數字。
這意味著比率是在運行時計算的,您可以通過僅更新--font-scale
屬性的值來更改它們。 例如:
@media screen and (min-width: 800px) { :root { --font-scale: 1.33; } }
如果您想更改比例,這比再次計算所有值要聰明、簡潔且快得多。 這也是我在生產代碼中不會做的事情。
儘管上面的示例對原型設計很有用,但在生產中,我更希望看到這樣的內容:
:root { --font-size-1: 1.728rem; --font-size-2: 1.44rem; --font-size-3: 1.2em; --font-size-4: 1em; } @media screen and (min-width: 800px) { :root { --font-size-1: 2.369rem; --font-size-2: 1.777rem; --font-size-3: 1.333rem; --font-size-4: 1rem; } }
與比爾文章中的示例類似,我發現查看實際值是有幫助的。 我們閱讀代碼的次數比編寫代碼的次數要多得多,並且字體比例等全局值在生產中很少更改。
上面的例子仍然不完美。 它違反了之前的規則,即全局值應該是靜態的。 我更喜歡使用預處理器變量並使用前面演示的技術將它們轉換為本地動態自定義屬性。
避免我們從使用一個自定義屬性到另一個自定義屬性的情況也很重要。 當我們這樣命名屬性時,就會發生這種情況。
改變值而不是變量
更改值而不是變量是有效使用自定義屬性的最重要策略之一。
作為一般規則,您永遠不應更改用於任何單一目的的自定義屬性。 這很容易做到,因為這正是我們使用預處理器做事的方式,但是對於自定義屬性來說意義不大。
在此示例中,我們有兩個自定義屬性用於示例組件。 我根據屏幕大小從使用--font-size-small
的值切換到--font-size-large
的值。
:root { --font-size-small: 1.2em; --font-size-large: 2em; } .example { font-size: var(--font-size-small); } @media screen and (min-width: 800px) { .example { font-size: var(--font-size-large); } }
一個更好的方法是定義一個範圍為組件的自定義屬性。 然後使用媒體查詢或任何其他選擇器更改其值。
.example { --example-font-size: 1.2em; } @media screen and (min-width: 800px) { .example { --example-font-size: 2em; } }
最後,在一個地方,我使用了這個自定義屬性的值:
.example { font-size: var(--example-font-size); }
在此示例和之前的其他示例中,媒體查詢僅用於更改自定義屬性的值。 您可能還注意到只有一處使用了var()
語句,並且更新了常規 CSS 屬性。
變量聲明和屬性聲明之間的這種分離是有意的。 這有很多原因,但在考慮響應式設計時,好處是最明顯的。
具有自定義屬性的響應式設計
當響應式設計嚴重依賴媒體查詢時,其中一個困難是,無論您如何組織 CSS,與特定組件相關的樣式在樣式表中都會變得支離破碎。
很難知道哪些 CSS 屬性會發生變化。 儘管如此,CSS 自定義屬性可以幫助我們組織一些與響應式設計相關的邏輯,並使處理媒體查詢變得更加容易。
如果它改變,它是一個變量
使用媒體查詢更改的屬性本質上是動態的,自定義屬性提供了在 CSS 中表達動態值的方法。 這意味著如果您使用媒體查詢來更改任何 CSS 屬性,您應該將此值放在自定義屬性中。
然後,您可以將其連同所有媒體規則、懸停狀態或定義值如何變化的任何動態選擇器一起移動到文檔頂部。
將邏輯與設計分離
如果做得正確,邏輯和設計的分離意味著媒體查詢僅用於更改自定義屬性的值。 這意味著與響應式設計相關的所有邏輯都應該在文檔的頂部,並且無論我們在 CSS 中看到var()
語句的什麼地方,我們都會立即知道這個屬性發生了變化。 使用傳統的 CSS 編寫方法,無法一眼就知道這一點。
我們中的許多人都非常擅長一目了然地閱讀和解釋 CSS,同時在腦海中跟踪哪些屬性在不同情況下發生了變化。 我已經厭倦了,我不想再這樣做了! 自定義屬性現在提供了邏輯與其實現之間的鏈接,因此我們不需要跟踪它,這非常有用!
邏輯折疊
在文檔或函數的頂部聲明變量的想法並不是一個新想法。 這是我們在大多數語言中所做的事情,現在我們也可以在 CSS 中做到這一點。 以這種方式編寫 CSS 會在文檔頂部和底部的 CSS 之間創建清晰的視覺區別。 當我談論它們時,我需要一種方法來區分這些部分,“邏輯折疊”的概念是我開始使用的一個隱喻。
首屏包含所有預處理器變量和自定義屬性。 這包括自定義屬性可以具有的所有不同值。 應該很容易跟踪自定義屬性的變化。
首屏下的 CSS 簡單明了,具有高度的聲明性且易於閱讀。 感覺就像在媒體查詢和現代 CSS 的其他必要復雜性之前的 CSS。
看一個非常簡單的六列 flexbox 網格系統示例:
.row { --row-display: block; } @media screen and (min-width: 600px) { .row { --row-display: flex; } }
--row-display
自定義屬性最初設置為block
。 超過 600 像素的顯示模式設置為 flex。
首屏下方可能如下所示:
.row { display: var(--row-display); flex-direction: row; flex-wrap: nowrap; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6 { flex-grow: 0; flex-shrink: 0; } .col-1 { flex-basis: 16.66%; } .col-2 { flex-basis: 33.33%; } .col-3 { flex-basis: 50%; } .col-4 { flex-basis: 66.66%; } .col-5 { flex-basis: 83.33%; } .col-6 { flex-basis: 100%; }
我們立即知道--row-display
是一個變化的值。 最初,它將是block
,因此將忽略 flex 值。
這個例子相當簡單,但是如果我們將它擴展為包含一個靈活寬度的列來填充剩餘空間,則可能需要將flex-grow
、 flex-shrink
和flex-basis
值轉換為自定義屬性。 你可以試試這個或者看看這裡更詳細的例子。
主題的自定義屬性
我主要反對對全局動態變量使用自定義屬性,並希望暗示將自定義屬性附加到:root
選擇器在許多情況下被認為是有害的。 但是每條規則都有一個例外,對於自定義屬性,它是主題。
有限地使用全局自定義屬性可以使主題化變得更加容易。
主題化通常是指讓用戶以某種方式自定義 UI。 這可能類似於更改個人資料頁面上的顏色。 或者它可能是更本地化的東西。 例如,您可以在 Google Keep 應用程序中選擇便箋的顏色。
主題化通常涉及編譯單獨的樣式表以覆蓋用戶偏好的默認值,或者為每個用戶編譯不同的樣式表。 這兩者都可能很困難,並且會對性能產生影響。
使用自定義屬性,我們不需要編譯不同的樣式表; 我們只需要根據用戶的喜好更新屬性的值。 由於它們是繼承值,如果我們在根元素上執行此操作,它們可以在應用程序的任何地方使用。
資本化全局動態屬性
自定義屬性區分大小寫,並且由於大多數自定義屬性都是本地的,因此如果您使用全局動態屬性,則將它們大寫是有意義的。
:root { --THEME-COLOR: var(--user-theme-color, #d33a2c); }
變量的大寫通常表示全局常量。 對我們來說,這意味著該屬性是在應用程序的其他地方設置的,我們可能不應該在本地更改它。
避免直接設置全局動態屬性
自定義屬性接受後備值。 避免直接覆蓋全局自定義屬性的值並將用戶值分開可能很有用。 我們可以使用後備值來做到這一點。
上面的示例將--THEME-COLOR
的值設置為--user-theme-color
的值(如果存在)。 如果--user-theme-color
未設置,將使用#d33a2c
的值。 這樣,我們不需要在每次使用--THEME-COLOR
時都提供回退。
您可能期望在下面的示例中背景將設置為green
。 但是,-- --user-theme-color
的值沒有在根元素上設置,所以--THEME-COLOR
的值沒有改變。
:root { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }
像這樣間接設置全局動態屬性可以保護它們不被本地覆蓋,並確保始終從根元素繼承用戶設置。 這是一個有用的約定,可以保護您的主題值並避免意外繼承。
如果我們確實想將特定屬性公開給繼承,我們可以將:root
選擇器替換為*
選擇器:
* { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }
現在為每個元素重新計算--THEME-COLOR
的值,因此可以使用--user-theme-color
的本地值。 換句話說,此示例中的背景顏色將為green
。
您可以在使用自定義屬性操作顏色部分中查看此模式的一些更詳細的示例。
使用 JavaScript 更新自定義屬性
如果你想使用 JavaScript 設置自定義屬性,有一個相當簡單的 API,它看起來像這樣:
const elm = document.documentElement; elm.style.setProperty('--USER-THEME-COLOR', 'tomato');
在這裡,我在文檔元素上設置--USER-THEME-COLOR
的值,或者換句話說,將由所有元素繼承的:root
元素。
這不是一個新的 API; 它與更新元素樣式的 JavaScript 方法相同。 這些是內聯樣式,因此它們將比常規 CSS 具有更高的特異性。
這意味著很容易應用本地自定義:
.note { --note-color: #eaeaea; } .note { background: var(--note-color); }
在這裡,我為--note-color
設置了一個默認值,並將其範圍限定為.note
組件。 即使在這個簡單的示例中,我也將變量聲明與屬性聲明分開。
const elm = document.querySelector('#note-uid'); elm.style.setProperty('--note-color', 'yellow');
然後,我以.note
元素的特定實例為目標,並僅為該元素更改--note-color
自定義屬性的值。 這現在將具有比默認值更高的特異性。
您可以使用 React 在此示例中看到它是如何工作的。 這些用戶偏好可以保存在本地存儲中,或者在更大的應用程序的情況下,可以保存在數據庫中。
使用自定義屬性操作顏色
除了十六進制值和命名顏色之外,CSS 還具有顏色函數,例如rgb()
和hsl()
。 這些允許我們指定顏色的各個組成部分,例如色調或亮度。 自定義屬性可以與顏色函數結合使用。
:root { --hue: 25; } body { background: hsl(var(--hue), 80%, 50%); }
這很有用,但是預處理器的一些最廣泛使用的特性是高級顏色函數,它允許我們使用諸如變亮、變暗或去飽和等函數來操縱顏色:
darken($base-color, 10%); lighten($base-color, 10%); desaturate($base-color, 20%);
在瀏覽器中擁有其中一些功能會很有用。 它們即將到來,但在我們在 CSS 中擁有原生顏色修改功能之前,自定義屬性可以填補一些空白。
我們已經看到自定義屬性可以在rgb()
和hsl()
等現有顏色函數中使用,但它們也可以在calc()
中使用。 這意味著我們可以通過乘以將實數轉換為百分比,例如calc(50 * 1%)
= 50%
。
:root { --lightness: 50; } body { background: hsl(25, 80%, calc(var(--lightness) * 1%)); }
我們要將亮度值存儲為實數的原因是我們可以在將其轉換為百分比之前使用calc
對其進行操作。 例如,如果我想將顏色變暗20%
,我可以將其亮度乘以0.8
。 我們可以通過將亮度計算分離到一個局部範圍的自定義屬性中來使它更容易閱讀:
:root { --lightness: 50; } body { --lightness: calc(var(--lightness * 0.8)); background: hsl(25, 80%, calc(var(--lightness) * 1%)); }
我們甚至可以抽像出更多的計算,並使用自定義屬性在 CSS 中創建類似顏色修改函數的東西。 對於大多數實際的主題化案例來說,這個示例可能過於復雜,但它展示了動態自定義屬性的全部功能。
簡化主題
使用自定義屬性的優點之一是能夠簡化主題。 應用程序不需要知道如何使用自定義屬性。 相反,我們使用 JavaScript 或服務器端代碼來設置自定義屬性的值。 這些值的使用方式由樣式表決定。
這再次意味著我們能夠將邏輯與設計分開。 如果您有技術設計團隊,作者可以更新樣式表並決定如何應用自定義屬性,而無需更改一行 JavaScript 或後端代碼。
自定義屬性還允許將一些主題的複雜性轉移到 CSS 中,這種複雜性會對 CSS 的可維護性產生負面影響,因此請記住盡可能保持簡單。
立即使用自定義屬性
即使您支持 IE10 和 11,您也可以立即開始使用自定義屬性。 本文中的大多數示例都與我們如何編寫和構建 CSS 有關。 在可維護性方面的好處是顯著的,但是,大多數示例只是減少了使用更複雜的代碼可以完成的工作。
我使用一個名為 postcss-css-variables 的工具將自定義屬性的大部分功能轉換為相同代碼的靜態表示。 其他類似的工具會忽略媒體查詢或複雜選擇器中的自定義屬性,將自定義屬性視為預處理器變量。
這些工具不能做的是模擬自定義屬性的運行時特性。 這意味著沒有動態特性,如使用 JavaScript 進行主題化或更改屬性。 在許多情況下,這可能沒問題。 根據具體情況,UI 自定義可能被視為漸進式增強,而默認主題對於舊版瀏覽器來說是完全可以接受的。
加載正確的樣式表
您可以通過多種方式使用 postCSS。 我使用gulp
過程為新舊瀏覽器編譯單獨的樣式表。 我的gulp
任務的簡化版本如下所示:
import gulp from "gulp"; import sass from "gulp-sass"; import postcss from "gulp-postcss"; import rename from "gulp-rename"; import cssvariables from "postcss-css-variables"; import autoprefixer from "autoprefixer"; import cssnano from "cssnano"; gulp.task("css-no-vars", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssvariables(), cssnano()])) .pipe(rename({ extname: ".no-vars.css" })) .pipe(gulp.dest("./dist/css")) ); gulp.task("css", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssnano()])) .pipe(rename({ extname: ".css" })) .pipe(gulp.dest("./dist/css")) );
這會產生兩個 CSS 文件:一個具有自定義屬性( styles.css
)的常規文件和一個用於舊瀏覽器的( styles.no-vars.css
)。 我希望為 IE10 和 11 提供styles.no-vars.css
和其他瀏覽器以獲取常規 CSS 文件。
通常,我提倡使用功能查詢,但 IE11 不支持功能查詢,而且我們已經廣泛使用自定義屬性,因此在這種情況下提供不同的樣式表是有意義的。
智能地提供不同的樣式表並避免一閃而過的無樣式內容並不是一項簡單的任務。 如果您不需要自定義屬性的動態特性,您可以考慮提供所有瀏覽器styles.no-vars.css
並將自定義屬性簡單地用作開發工具。
如果你想充分利用自定義屬性的所有動態特性,我建議使用關鍵的 CSS 技術。 按照這些技術,主樣式表是異步加載的,而關鍵的 CSS 是內聯渲染的。 您的頁眉可能如下所示:
<head> <style> /* inlined critical CSS */ </style> <script> loadCSS('non-critical.css'); </script> </head>
我們可以擴展它來加載styles.css
或styles.no-vars.css
,這取決於瀏覽器是否支持自定義屬性。 我們可以像這樣檢測支持:
if ( window.CSS && CSS.supports('color', 'var(--test)') ) { loadCSS('styles.css'); } else { loadCSS('styles.no-vars.css'); }
結論
如果您一直在努力有效地組織 CSS,在響應式組件方面遇到困難,想要實現客戶端主題,或者只是想從自定義屬性開始,那麼本指南應該會告訴您您需要知道的一切。
歸結為理解 CSS 中動態變量和靜態變量之間的區別以及一些簡單的規則:
- 將邏輯與設計分離;
- 如果 CSS 屬性發生變化,請考慮使用自定義屬性;
- 更改自定義屬性的值,而不是使用哪個自定義屬性;
- 全局變量通常是靜態的。
If you follow these conventions, you will find that working with custom properties is a whole lot easier than you think. This might even change how you approach CSS in general.
延伸閱讀
- “It's Time To Start Using Custom Properties,” Serg Hospodarets
A general introduction to the syntax and the features of custom properties. - “Pragmatic, Practical, And Progressive Theming With Custom Properties,” Harry Roberts
More useful information on theming. - Custom Properties Collection, Mike Riethmuller on CodePen
A number of different examples you can experiment with.