【譯】CSS 自定義屬性的策略指南

原文地址: www.smashingmagazine.com/2018/05/css…javascript

譯文地址: github.com/yued-fe/y-t…css

譯者: 波波html

校對者: ziven27小爺前端

關於做者 Mike 是來自澳大利亞的獨立網站開發人員,曾在澳大利亞的一些大型網站和一些小型社區工做過...... 關於Michael的更多信息...java

CSS 自定義屬性(也稱爲「CSS 變量」),在目前全部的現代瀏覽器中都獲得了支持,開發者已經在項目中開始使用,可是它們與預處理器中的變量不一樣,雖然我已經看到過不少例子,卻沒有搞清楚他們真正的優點在哪裏。git

自定義屬性有很大的潛力能夠改變咱們編寫和組織 CSS 的方式,而且在必定程度上改變 JavaScript 與 UI 組件的調用方式。我並不關心語法和它們的工做方式(爲此,我建議你閱讀「如今是時候開始使用自定義屬性了」這篇文章)。同時我想更深刻地研究如何充分利用 CSS 自定義屬性。github

自定義屬性與預處理器中的變量有何類似之處?

自定義屬性有點像預處理器中的變量,但仍是有很大的差異。最重要也是最明顯的區別是在於語法。web

在 SCSS 中咱們用 $ 符號來定義變量:數據庫

$smashing-red: #d33a2c;
複製代碼

在 Less 中咱們用 @ 符號:gulp

@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()。

關於語法和自定義屬性的工做原理可能還有不少,好比如何使用 fallback 值,以及咱們還能夠將變量分配給其餘變量,本文介紹的基礎知識應該已經足以讓你們理解其中的概念。更多關於自定義屬性工做方式的詳細信息,能夠閱讀由 Serg Hospodarets 編寫的「是時候開始使用自定義屬性了」這篇文章。

動態變量與靜態變量

拋開樣式差別,預處理器中的變量和自定義屬性中的變量之間最大的差異是做用域。咱們能夠將變量根據做用域分爲靜態變量和動態變量兩個部分。預處理器中的變量是靜態的,而自定義屬性是動態的。

在 CSS 中,靜態意味着你能夠在編譯過程當中更新變量的值,可是這不能改變它以前的值。

$background: blue;
.blue {
  background: $background;
}
$background: red;
.red {
  background: $background;
}
複製代碼

結果是:

.blue {
  background: blue;
}
.red {
  background: red;
}
複製代碼

一旦編譯成了 CSS,這個變量就會消失。這意味着咱們在讀取一個 .scss 文件並輸出的時候並不須要關心 HTML、瀏覽器或其它輸入,而自定義屬性並不是如此。 預處理器確實有一種「塊級做用域」,其中變量能夠在選擇器,函數或 mixin 中臨時更改。這改變了塊內變量的值,但它仍然是靜態的。這與塊有關,而不是選擇器。在下面的例子中,變量 $background 在 .example 類內部被改變。即便咱們使用相同的選擇器,它也會變回塊級做用域以外的初始值。

$background: red;
.example {
  $background: blue;
  background: $background;
}

.example {
  background: $background;
}
複製代碼

編譯後:

.example {
  background: blue;
}
.example {
  background: red;
}
複製代碼

自定義屬性不一樣於預處理器。在涉及自定義屬性的地方,動態範圍意味着它們受到繼承和級聯的影響。屬性與選擇器綁定,若是值發生變化,就會像其餘 CSS 屬性同樣影響全部匹配的 DOM 元素。

這聽起來很贊,由於你能夠在媒體查詢中,經過使用相似 hover 的僞類選擇器甚至是 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 也同樣。有全局的變量,也有局部的變量。品牌顏色、垂直間距、排版方式,這些你可能會但願都能在 app 端和網頁中全局調用。固然也有一些局部的東西,好比,按鈕組件可能具備大小尺寸。你不但願這些按鈕的大小適用於全部輸入元素或頁面上的每一個元素。

這是咱們在 CSS 中熟悉的應用場景。咱們開發了設計系統、命名規範和 JavaScript 庫,這些能夠分離局部組件和全局組件。自定義屬性給這類問題提供了新的思路。

一般 CSS 自定義屬性的範圍侷限於咱們指定的選擇器中。這看起來有點像局部變量。可是,自定義屬性具備繼承的特性,因此在大多數狀況下,它們表現的更像全局變量 —— 特別是在應用於 :root 選擇器的時候。這意味着咱們須要考慮如何使用它們。

大量的示例都表示將自定義屬性應用到 :root 元素上,對於 Demo 這還說得過去,但它可能會污染全局做用域,從而致使意外的繼承問題。幸運的是,咱們已經吸收了教訓。

全局變量趨於靜態

可能會有一些例外,但一般來講,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;
}
複製代碼

顯然,若是我屢次使用變量或從變量計算獲得 margin 或 padding 值,這個示例將更有意義。然而,快速原型化不一樣尺寸的能力多是一個充分的理由。

由於大多數靜態變量都是全局的,因此我喜歡區分只在組件內部使用的靜態變量。爲此,能夠在這些變量前面加上組件名,或者可使用另外一個前綴,如組件的 c 變量名或 l 變量名。您可使用任何您想要的前綴,或者能夠在全局變量前面加上前綴。不管您選擇什麼,區分都是頗有幫助的,特別是當轉換一個現有的代碼來使用自定義屬性時。

什麼時候使用自定義屬性

若是能夠在組件內部使用靜態變量,那麼何時應該使用自定義屬性呢? 將現有的預處理器變量轉換爲自定義屬性一般沒什麼意義。畢竟,自定義屬性的用途是徹底不一樣的。當咱們有 CSS 屬性時,自定義屬性是有意義的,尤爲是在 DOM 中(尤爲是動態條件),例如 :fouces、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 類的按鈕大小更改成 600px 以上。最後,我將這個自定義屬性應用到一個位置的全部按鈕元素。

不要聰明過頭

自定義屬性的動態特性容許咱們建立一些聰明而複雜的組件。

隨着預處理器的推出,咱們中的許多人可使用 mixin 和自定義函數建立具備巧妙抽象的庫。在有限的狀況下,像這樣的例子仍然有用,但大多數狀況下,隨着使用預處理器的時間越長,我使用的功能就越少。如今,我使用預處理器的場景幾乎只在靜態變量的部分。

自定義屬性一樣也會出現這樣的情況,我期待看到更多聰明的例子。但從長遠來看,可讀和可維護的代碼總會是更好的選擇(至少在項目中是這樣)。

我最近在 Free Code Camp Medium 上閱讀了關於此主題的優秀文章。它是由 Bill Sourour 撰寫的,叫‘ Don’t Do It At Runtime. Do It At Design Time ’。與其解釋他的觀點,我更推薦你先去看一下這篇文章。

預處理變量和自定義屬性之間的一個關鍵區別是:自定義屬性在運行時工做。這意味着,在複雜性方面自定義屬性是能夠被接受的,由於預處理器沒有自定義屬性這麼好的辦法。

最近我經常使用來舉例說明的一個例子是:

: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-size 屬性的值來更改它們。例如:

@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;     
  }
}
複製代碼

與 Bill 的文章中的例子相似,我發現查看實際值的含義頗有幫助。咱們閱讀代碼的次數比咱們編寫的次數多得多,並且字體等全局參數在項目中不多變化。

上面的例子還不算完美。它違反了之前的規則,即全局變量應該是靜態的。我更願意使用預處理器變量,並使用前面演示的例子將它們轉換爲局部動態自定義屬性。

避免使用一個自定義屬性到另外一個自定義屬性的狀況也很重要。當咱們命名這樣的屬性時,會發生這種狀況。

修改屬性值而不是變量

這是使用自定義屬性的最重要準則之一。

一般來講,不推薦修改僅有單一目的的自定義屬性。雖然這很容易實現,由於這正是預處理器應該作的事情,但對於自定義屬性來講這沒有多大意義。

在這個例子中,示例組件上咱們建立了兩個使用的自定義屬性。根據屏幕的尺寸變化,將自定義屬性 --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 屬性,自定義屬性是一個不錯的選擇。

而後,你能夠將媒體查詢規則、hover 的狀態或者任何定義了屬性值修改方式的動態選擇器,都移動到文檔的頂部。

將邏輯和設計分離

若是你的操做正確,那麼邏輯與設計的分離意味着媒體查詢只是用來改變自定義屬性的值。而這說明與響應性設計相關的全部邏輯都應該放到於文檔的頂部,而且不管咱們在哪裏看到 var() 聲明語句,咱們都能很明顯的知道這個屬性會發生變化。而使用傳統的 CSS 方式,咱們是沒法察覺這一點的。

咱們中的大多數人都很是擅長閱讀和理解 CSS,咱們須要思考在不一樣的狀態下哪些屬性發生了變化。我受夠這樣的方式了。如今自定義屬性幫咱們把邏輯和實現連接了起來,因此咱們不須要在大腦去跟蹤這些變化,這在項目中真的很是有用!

摺疊邏輯

在文檔或函數頂部聲明變量的想法是很早就有的方式。也是大多數語言中推薦的作法,如今咱們也能夠在 CSS 中完成。以這種方式編寫 CSS,光從視覺角度就很容易區分頂部和以後的代碼。我要使用它們時我能很方便的找到它們。而這就是我強調的「摺疊邏輯」的概念。 在這個摺疊的上方包含全部預處理器變量和自定義屬性。這包含了全部的參數和自定義屬性可能的變化。這樣咱們就很容易的知道自定義屬性發生了哪些變化。

在這段摺疊以後的 CSS 代碼可讀性也是很高的,這就和你原來寫媒體查詢,和其它必要的代碼同樣沒有什麼區別。

咱們再來看一個關於六列 Flexbox 佈局的網格系統很是簡單的例子:

.row {
  --row-display: block;
}
@media screen and (min-width: 600px) {
  .row {
    --row-display: flex;
  }
}
複製代碼

這個 --row-display 自定義屬性的初始值設置爲 block。當屏幕超過 600px 以後這個值會被重置爲 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。可是在 :root 做用域下的 --user-theme-color 的值並無設置,因此最後 --THEME-COOR 值不會發生變化。

: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 和 IE11,你也可使用自定義屬性。本文中的大多數示例都與如何編寫和構建 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 和 IE11 瀏覽器中使用 styles.no-vars.css 這個文件,其它瀏覽使用常規的 CSS 文件。

一般,我主張使用功能查詢,但「 IE11 不支持功能查詢」,但是咱們已經大量使用了自定義屬性,在這種狀況下,爲其提供不一樣樣式表就顯得有意義了。

經過提供不一樣的樣式表以免無樣式內容的閃爍並非一個簡單的事情。若是不須要自定義屬性中的動態特性,能夠考慮僅使用適用於全部瀏覽器的 styles.no-vars.css 文件,而自定義屬性僅做爲開發工具。

若是你想充分利用自定義屬性的全部動態特性,我建議使用「關鍵的 CSS 技術」。基於這些技術,主樣式表是異步加載的,而關鍵的 CSS 是內聯的。你的頁面的 header 可能看起來像這樣:

<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 屬性須要發生變化,能夠考慮使用自定義屬性;
  • 僅修改自定義屬性的值,而不是修改自定義屬性自己;
  • 全局變量一般是靜態的。

若是理解了以上這些條例,你會發現使用自定義屬性比你想象的要容易得多,甚至可能會改變你對 CSS 的處理方式。

進一步閱讀

是時候使用自定義屬性了 」,Serg Hospodarets 介紹的自定義屬性的語法和特性的。
漸進式實用的CSS自定義屬性 」,Harry Roberts 介紹的更多關於主題的有用信息。
自定義屬性集合」,Mike Riethmuller 在 CodePen 上提供的大量的不一樣的示例。

查看更多分享,請關注閱文集團前端團隊公衆號:

相關文章
相關標籤/搜索