[CSS] 自定義變數帶你隨心所欲,一鍵換膚

Apollozz發表於2019-02-17

認識CSS自定義變數

CSS自定義屬性?聽著怎麼那麼神奇呢,屬性還可以自定義,那不是可以放肆地玩耍?我自己定義的屬性瀏覽器都能認識?

一、基礎

(一)名字和用途

其實CSS自定義屬性還有很多小名,比如CSS變數原生變數CSS自定義屬性級聯變數,這些指的都是同個東西。

說到變數,在SCSS\LESS等CSS前處理器中大家都已經經常運用,說來有幾點好處:

1、能使用顏色變數統一風格;
2、可以採用一致的元件屬性,包括佈局和定位等;
3、避免程式碼冗餘。

那既然SCSS就能做到的東西,我們還有這個所謂的CSS自定義變數幹什麼呢?人家自然有它的獨到之處。

1、比如可以在執行時改寫,具備動態性;
2、比如方便使用JS讀取和改寫;
3、比如可繼承、可組合、同時具有作用域。

在這幾個方面,前處理器完全被CSS自定義變數KO了。

(二)宣告變數

語法很簡單,分成兩步,宣告變數使用變數

宣告變數使用的是--字首。

:root{
    --*: xxxx;
    /* --variety-name: variety-value; */
}
複製程式碼

這裡需要注意幾個點:

  1. :root匹配的就是HTML中的<html>元素,具有最高的權重,:root宣告的變數就是全域性變數;
  2. CSS自定義變數對大小寫敏感,--color--Color是兩個變數;
  3. 變數必須宣告在{}中,如果在這裡把它理解為屬性就更好記憶了,畢竟我們不會把CSS屬性寫到括號外邊去;
  4. *號代表的就是我們給變數起的名字。起名字這事真的很煩人,還好CSS變數的名稱限制很少,除了一些特殊關鍵字元不能使用,正常來說你用數字/字母/下劃線_/短橫線-都是沒問題的,據說還可以使用中文、日文和韓文。
:root{
    --黑色:#000;
}

body{
    background: var(--黑色);
}
複製程式碼

手賤如我試了一下,真的可以識別。但是,為了世界和平請答應我不要這麼寫。

(三)使用變數

很簡單,就是我們很熟悉的var關鍵字。

/* 定義變數 */
:root{
    --*: #000;
    /* 例如 --color-bg: #000; */
}

/* 使用變數 */
body{
    background: var(--*);
    /* 例如 background: var(--color-bg); */
}
複製程式碼

還有一種設定預設值的使用,就是在變數名稱後面,加上一個預設值。

.div{
    background: var(--變數名稱,[預設值]);
    /* 例如 background: var(--color-bg, #000); */
}
複製程式碼

也就是說,當這個變數沒有被宣告過的話,就會使用預設值,不至於沒著沒落的。

注意這裡的情況是變數沒有被宣告過,要是變數是宣告過的,但是使用起來是不合法的,那麼就會採用原來屬性的預設預設值,並不是後面這個你設定的預設值。

p{
    background-color: var(--color, #000);
    /* --color沒有宣告過,所以這裡的p元素背景顏色時候用了預設值#000 */
}
複製程式碼
div{
    --color: 20px;
    background-color: var(--color, #000);
    /* 很明顯,background-color: 20px;是有語法錯誤的,所以這裡div的背景色為透明,取的是這個屬性的預設值 */
}
複製程式碼

(四)作用域和權重

1、如果你需要定義一個全域性的變數,那麼可以放在:root根元素下面;
2、如果只需要在部分元素/元件下使用,就定義在相關的類下面;
3、另外還可以在@media媒體查詢中或者:hover等偽類中使用。

理解了作用域,那麼權重也是同樣的道理,因為CSS自定義變數是可以繼承的,所以權重跟我們平時的屬性權重理解是一樣的。

這裡引用一下張鑫旭大神的例子給大家出道題。

:root{
    --color: purple;
}
div{
    --color: green;
}
#alert{
    --color: red;
}
*{
    color: var(--color);
}
複製程式碼
<p>請問我是什麼顏色</p>
<div>請問我是什麼顏色</div>
<div id="alert">
    請問我是什麼顏色
    <p>請問我是什麼顏色</p>
    <p style="--color: grey;">請問我是什麼顏色</p>
</div>
複製程式碼

答案如下:

[CSS] 自定義變數帶你隨心所欲,一鍵換膚

(五)變數的其他組合

除了上述的一些用法,CSS自定義變數也可以使用calc()函式進行計算,或者進行字串拼接。

這裡舉了三個例子,具體的說明在註釋中。

p{
    --fz: 50;
    font-size: var(--fz)px;
    /* 不要太天真,這樣是錯的 */
}
複製程式碼
p{
    --fz: 50;
    font-szie: calc(var(--fz) * 1px);
    /* 如果你一定要這麼用,可以使用calc計算函式 */
}
複製程式碼
p::after{
    --text: "hellp";
    content: var(--text) " word";
    /* 但是字串的拼接是可以實現的 */
}
複製程式碼

當然,變數不止可以直接使用,直接或者通過計算把值傳遞給另一個變數也是可行的。

p{
    --fz: 20px;
    --fz-lg: var(--fz);
    font-size: var(--fz-lg);
    /* 直接傳遞 */
}
複製程式碼
p{
    --fz: 20px;
    --fz-lg: calc(var(--fz) * 1.5);
    font-size: var(--fz-lg);
    /* 通過計算後傳遞 */
}
複製程式碼

二、在JS中使用

讀:getPropertyValue( )

寫:setProperty( )

比如說,你在:root上定義了一個color變數,用於設定頁面的主題色,那麼通過下面的JS,你就可以很簡單地改變color變數的值,從而改變頁面的主題色。換個皮膚,so easy。

// 讀取資料
const rootStyles = getComputedStyle(document.documentElement);
const varValue = rootStyles.getPropertyValue('--color').trim();

// 改寫資料
document.documentElement.style.setProperty('--color', value);
複製程式碼

當然用處不止用來換膚,充分發揮你的想象力,看看CSS變數與JS的結合能產生什麼樣奇妙的效果~附上大漠老師的小DEMO

[CSS] 自定義變數帶你隨心所欲,一鍵換膚

應用CSS自定義變數

介紹了這麼多,那麼CSS自定義變數到底都在什麼場景下應用呢?

一、CSS禪意花園

最強的應用當然是一鍵換膚啦!

一般情況下,我們若是想根據不同的合作方或者不同的應用更換主題顏色,一般使用前處理器先定義一個全域性主題顏色,如:

$theme-color: #f00;

button{
    background: $theme-color;
}
複製程式碼

最後編譯得到一個定製的CSS檔案,如:

button{
    background: #f00;
}
複製程式碼

這樣我們就可以通過引入這個特殊的CSS檔案,得到一套主題色為紅色的頁面樣式。

第一種形式適用於,功能通用但是最後根據配置輸出一個產品的獨立管理臺一個獨立小程式等,這樣用自己一套獨有的CSS檔案就很方便。

但是如果我們提供一個通用的產品去接入不同的合作方,接入方都有定製主題的需求,就可以選擇前處理器或CSS自定義屬性。

/* 前處理器方式,先定義不同的主題色 */
$theme-color-a: #f00;
$theme-color-b: #0f0;

/* 在頁面層級最外層加上定製的類名,類名中所有樣式都需要重新覆蓋一遍 */
.project-a{
    button{
        background: $theme-color-a;
    }
    a{
        color: $theme-color-a;
    }
}
.project-b{
    button{
        background: $theme-color-b;
    }
    a{
        color: $theme-color-b;
    }
}

/* 或者在媒體查詢中需要重置樣式 */
$fz-sm: 12px;
$fz-md: 14px;

button{
    font-size: $fz-sm;
}
a{
    font-size: $fz-sm;
}

@media (min-width: 375px){
    button{
        font-size: $fz-md;
    }
    a{
        font-size: $fz-md;
    }
}
複製程式碼
:root{
    --FZ: 12px;
}
/* 照常寫一套樣式 */
button{
    background: var(--THEME-COLOR, #fff);
}
a{
    color: var(--THEME-COLOR, #fff);
}

/* 根據不同的接入方設定主題色 */
.project-a{
    --THEME-COLOR: #f00;
}
.project-b{
    --THEME-COLOR: #0f0;
}

@media (min-width: 375px){
    :root{
        --FZ: 14px;
    }
}
複製程式碼

這種需求在CSS前處理器中無法實現一個沒有複製程式碼的方案,總是需要覆蓋實現的值和規則,這也經常會導致CSS冗餘。

使用CSS自定義屬性,解決方案是儘可能的簡潔,也避免複製和貼上程式碼,因為只要重新定義變數的值,不需要去覆蓋一次樣式。

第二種方式中兩個方式的區別則是,前處理器中變數的作用域是無法繼承的,而CSS自定義變數則相對靈活,這樣一旦接入方多了之後,兩種方式的程式碼量就會有質的區別。

以上兩種方式都還是直接把主題色配置在CSS中。但是如果需要接入管理臺,讓合作方直接在管理臺設定主題色的話,前處理器這種靜態的方式就直接退出了競爭,CSS自定義屬性的絕對優勢就出來了。

:root{
    --THEME-COLOR: #fff;
}
button{
    background: var(--THEME-COLOR);
}
a{
    color: var(--THEME-COLOR);
}
複製程式碼
let value = #f00; //或者通過請求獲取配置的主題色
document.documentElement.style.setProperty('--THEME-COLOR', value);
複製程式碼

第三種方式則非常靈活,我們可以通過管理臺或者其他配置的方式,傳遞合作方主題色,一鍵應用即可。

歷史性的時刻誕生了,從此接入就是分分鐘的事,直接讀取管理臺的配置,更改CSS自定義屬性。來個小DEMO體驗一下吧。

[CSS] 自定義變數帶你隨心所欲,一鍵換膚
codepen.io/Apollozz/pe…

二、主題色處理

前處理器提供了很多高階的顏色方法,可以實現顏色的高亮、變暗或去飽和等等;

$color: #f00; 

.lighten{
    background: lighten($color,10%);
}
.darken{
    background: darken($color,10%);
}
.desaturate{
    background: desaturate($color,10%);
}
複製程式碼

這樣編譯出來的結果就是

.lighten{
    background: #ff3333;
}
.darken{
    background: #cc0000;
}
.desaturate{
    background: #f20d0d;
}
複製程式碼

這些方法無法直接使用在CSS自定義屬性中,但是我們可以通過rgb( )或者hsl( )來調整主題顏色的色調或亮度。

(一)使用rgb改變顏色

rgb顏色變亮變暗的原理相對簡單,只需修改--COLOR-R/--COLOR-G/--COLOR-B的值,利用calc函式對rgb的值進行線性增減即可。

:root{
  --COLOR-R: 25;
  --COLOR-G: 153;
  --COLOR-B: 112;
  --DARKEN: 30; // 加深程度
  --LIGHTEN: 30; // 變亮程度
  --THEME-COLOR: rgb(var(--COLOR-R), var(--COLOR-G), var(--COLOR-B));
  --THEME-COLOR-DARKEN: rgb(calc(var(--COLOR-R) - var(--DARKEN)), calc(var(--COLOR-G)  - var(--DARKEN)), calc(var(--COLOR-B) - var(--DARKEN)));
  --THEME-COLOR-LIGHTEN: rgb(calc(var(--COLOR-R) + var(--LIGHTEN)), calc(var(--COLOR-G)  + var(--LIGHTEN)), calc(var(--COLOR-B) + var(--LIGHTEN)));
}
複製程式碼

[CSS] 自定義變數帶你隨心所欲,一鍵換膚
codepen.io/Apollozz/pe…

(二)使用hsl改變顏色

rgb我們可能相對熟悉,但是hsl用得比較少,下面簡單介紹一下hsl的原理,詳細內容請點選連結。

與RGB使用的三色光不同,HSL同樣使用了3個分量來描述色彩,HSL色彩的表述方式是:H(hue)色相,S(saturation)飽和度,以及L(lightness)亮度。

HSL的H(hue)分量,代表的是人眼所能感知的顏色範圍,這些顏色分佈在一個平面的色相環上,取值範圍是0°到360°的圓心角,每個角度可以代表一種顏色。

HSL的S(saturation)分量,指的是色彩的飽和度,它用0%至100%的值描述了相同色相、明度下色彩純度的變化。數值越大,顏色中的灰色越少,顏色越鮮豔,呈現一種從理性(灰度)到感性(純色)的變化。

HSL的L(lightness)分量,指的是色彩的明度,作用是控制色彩的明暗變化。它同樣使用了0%至100%的取值範圍。數值越小,色彩越暗,越接近於黑色;數值越大,色彩越亮,越接近於白色。

一般來說,我們需要按鈕在hover狀態時加深顏色,此時應用的原理是將顏色的hsl值中的L也就是亮度調低。

:root{
  --COLOR-H: 29;
  --COLOR-S: 100;
  --COLOR-L: 50;
  --DARKEN: 0.15;
  --THEME-COLOR: hsl(var(--COLOR-H), calc(var(--COLOR-S) * 1%), calc(var(--COLOR-L) * 1%));
  --THEME-COLOR-DARKEN: hsl(var(--COLOR-H), calc(var(--COLOR-S) * 1%), calc(var(--COLOR-L) * (1 - var(--DARKEN)) *  1%));
  --THEME-COLOR-LIGHTEN: hsl(var(--COLOR-H), calc(var(--COLOR-S) * 1%), calc(var(--COLOR-L) * (1 + var(--DARKEN)) *  1%));
}
複製程式碼

[CSS] 自定義變數帶你隨心所欲,一鍵換膚
codepen.io/Apollozz/pe…

(三)使用遮罩改變顏色

如果不通過改變色值來改變顏色的話,可以選擇遮上一個半透明的蒙層來改變顏色,加深顏色則選擇黑色半透明蒙層,提亮顏色則選擇白色半透明蒙層。

.button_color{
    position: relative;
    color: #fff;
    background: var(--THEME-COLOR);
    border: 1px solid var(--THEME-COLOR);
    &:after{
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        transition: all 0.2s;
    }
    /* 黑色半透明蒙層 */
    &:hover:after{
        background: rgba(0,0,0,0.05);
    }
    /* 白色半透明蒙層 */
    &.lighten:hover:after{
        background: rgba(255,255,255,0.1);
    }
}
複製程式碼

[CSS] 自定義變數帶你隨心所欲,一鍵換膚
codepen.io/Apollozz/pe…

三、相容性

目前CSS自定義變數的相容性還是比較可觀的,新的主流瀏覽器都支援。

PC端主要是IE這塊硬石頭,而移動端則主要是低端機型系統例如ios9.2及以下/安卓4.4及以下不支援,這樣的相容性已經足以允許讓我們在專案中開始使用CSS自定義屬性,並對一些低端版本進行降低相容處理。

[CSS] 自定義變數帶你隨心所欲,一鍵換膚

對於不支援的瀏覽器可以採用下列相容方式:

方案1:直接設定一個預設顏色

直接使用普通的屬性定義,相容所有的瀏覽器,保證顯示正常。

button{
    background: #F00;
    /* 預設顏色,若不支援CSS自定義屬性則應用該預設顏色 */
    background: var(--THEME-COLOR, #F00);
    /* 在實踐中發現有些機型雖然能識別到CSS自定義變數但是無法獲取正確顏色,只能獲取到預設值 */
}
複製程式碼

方案2:CSS根據@supports判斷是否相容並適配

低端機型也無法正確識別@supports,@supports的相容性只比CSS自定義變數好一丟丟。

@supports ( (--a: 0)) {
  /* supported */
}

@supports ( not (--a: 0)) {
  /* not supported */
}
複製程式碼

方案3:js根據@supports判斷是否相容並適配

const isSupported =
  window.CSS &&
  window.CSS.supports &&
  window.CSS.supports('--a', 0);

if (isSupported) {
  /* supported 引入支援自定義變數的CSS,允許更改CSS自定義變數 */
} else {
  /* not supported 引入相容的CSS檔案 */
}
複製程式碼

四、注意事項

  1. 在一些瀏覽器中,針對CSS變數的複雜calc()運算可能不能工作;
  2. 進行calc()運算時,最好能提供預設值:calc(var(--base-line-height, 0) * 1rem)
  3. 不能作為媒體查詢值使用:
@media screen and (min-width: var(--desktop-breakpoint) ) { 
};
複製程式碼
  1. 圖片地址,如url( var(--image-url) ) ,不會生效;
  2. 因為CSS自定義變數對大小寫敏感,故建議全域性變數使用全大寫形式,除了設定整體主題色,儘量減少改動全域性變數;
  3. web端可將主題顏色等變數設定在根元素html,並通過上述方法修改自定義屬性的值;
  4. 小程式因無法獲取DOM,無法直接修改CSS自定義屬性的值,可以採取在頁面的最外層元素設定行內樣式的方式重置自定義屬性的值,如:
.container{
    --THEME-COLOR: #f00;
}
複製程式碼
<view class="container" style="--THEME-COLOR: #0f0;"> 
    <!-- 該結構下的元素,重置為行內樣式的主題色 -->
</view>
複製程式碼

五、小結

  1. CSS自定義變數目前已支援各主流瀏覽器,低端版本可以採用相容方案;
  2. SCSS變數和CSS自定義變數有本質上的區別,用來解決不同場景下的問題,CSS自定義屬性用於動態主題,前處理器變數用於靜態模板,專案中可以根據情況結合運用效果更佳;
  3. 在媒體查詢中使用自定義變數的話,這樣響應式設計相關的邏輯與正常的設計雖然分離,但是無論我們在哪裡看到var( )宣告語句,我們都能很明顯的知道這個屬性會發生變化。而使用傳統的CSS方式,我們是無法察覺這一點的,這樣程式碼的可讀性就高了很多。

參考資料

  1. 【譯】CSS自定義屬性的策略指南
  2. 深入學習CSS自定義屬性
  3. 小tips:瞭解CSS/CSS3原生變數var

相關文章