CSS變數的作用域和預設值

球球發表於2021-09-25

CSS變數的作用域是什麼?

變數作用域,變數的可用性範圍。變數並不總是有效可用的,而限定變數的可用性範圍就是變數的作用域。而CSS變數在CSS層次結構中宣告的位置,決定了它在整個層次結構中的可用性範圍。

通常來說,CSS變數僅對宣告它的元素的子元素可見。比如下面的例子中,--bgColor變數對child元素可見:

<div class="parent">
  parent
  <div class="child">child</div>
</div>
.parent {
    --bgColor: pink;
}
.child {
    background: var(--bgColor);
}


但如果反過來則不可見:

.child {
    --bgColor: pink;
}
.parent {
    background: var(--bgColor);
}

作用域型別

CSS變數遵循詞法作用域(靜態作用域)規則,有兩種作用域型別——全域性作用域區域性作用域

全域性作用域

:root中宣告的CSS變數即全域性作用域的變數,即可以在CSSOM中任意位置使用。

/* 定義全域性變數 */
:root{
  --primary-color: pink;
}

/* 任意位置都可以訪問全域性變數 */
.wrapper{
  background: var(--primary-color);
}

區域性作用域

而在其他CSS層級中宣告的變數僅對該CSS層級以及它的子級可見。

<div class="parent text">
  parent
  <div class="child text">child</div>
</div>
.parent {
  --fontSize: 24px;
  --lineHeight: 1.8;
}

.child {
  --fontSize: 18px;
  --lineHeight: 1.6;
}
.text {
  font-size: var(--fontSize);
  line-height: var(--lineHeight);
}


在上面對例子中,可以用相同的命名在不同的CSS塊中宣告和訪問變數。
區域性作用域總是可以訪問外層作用域或者全域性作用域的變數,相反則不可。

變數提升

和JavaScript一樣,CSS變數生命可以被提升,即CSS變數可以再宣告之前使用他們。在瀏覽器渲染相應的HTML元素樣式前,會將CSS變數的宣告提升並移動到CSSOM的最頂部。

body {
    background-color: var(--bgColor);
}

:root {
    --bgColor: pink;
}
body {
    background-color: var(--bgColor);
}

:root {
    --bgColor: pink;
}

如上面的例子中,CSS變數--bgColor可以在:root 偽類選擇器宣告之前使用。CSS變數可以先訪問再宣告這一特性,使得CSS變數成為一個非常強大的功能點。

變數預設值

如下面的例子中,被逗號分隔後的第二個值1.2為預設值,即如果--scale未被賦值則使用1.2這個數值。

.bigger {
  transform: scale(var(--scale, 1.2));
}

當使用var()函式時,可以分配一個或多個回退的屬性值(使用逗號分隔),比如設定字型:

html {
  font-family: var(--fonts, Helvetica, Arial, sans-serif);
}

還可以使用一連串的變數回退,但需要使用var()巢狀起來:

.bigger {
  transform: scale(var(--scale, var(--secondFallbackScale, 1.2));
}

如果--scale未被定義,會嘗試下一個值即--secondFallbackScale。如果--secondFallbackScale也未被定義,最終會回退到1.2。

模組化

CSS變數的強大之處在於,作用域的特性有助於設計一個程式碼整潔、模組化的系統。

當我們想要主題化某一模組時,可以在該模組的根元素中設定CSS變數,以便於變數可以向下傳遞,而不影響該模組以外的元素。

<html>
    <body>
        <div class="mod">
            <p>
                段落 1
            </p>
        </div>
        <p>
            段落 2
        </p>
    </body>
</html>
.mod {
    --modBgColor: pink;
    --modMainColor: blueviolet;
}

p {
    background: var(--modBgColor);
    color: var(--modMainColor);
}

Untitled 3.png

.mod中宣告的CSS變數將對<p>元素的“段落 1”可見,它是類名是mod節點的子元素,因此設定的背景色和字型色會對“段落 1”生效。

<p>元素的“段落2”樣式不會受到影響,因為它不屬於.mod或其子元素,CSS變數--modBgColor--modMainColor對它不可見。

這就是CSS變數作用域的強大之處。

變數賦值變數

CSS變數也可以使用變數賦值變數,下面的例子中,使用CSS變數渲染漸變背景色:

<div class="gradient"></div>
<div class="gradient"></div>
:root {
  --color1: pink;
  --color2: aquamarine;

  --bg: linear-gradient(to right, var(--color1), var(--color2));
}

.gradient {
  margin: 10px auto;
  width: 200px;
  height: 100px;
  background: var(--bg);
}

Untitled 4.png

變數不生效問題

現在,如果想讓第二個模組漸變色變成從粉色過渡到薰衣草色,那就簡單粗暴地給第二個模組加個類名theme-2,同時給變數—color2賦值薰衣草色:

.theme-2 {
  --color2: lavender;
}

然而變數並沒有生效,問題在於--bg是在:root中被宣告且被賦值了紅到綠的漸變值,可以使用這個變數是因為它是全域性變數可訪問,在theme-2中修改的—-color2的值並不會更新—-bg的值,轉換成js語句就容易理解了:

let color1 = 'red';
let color2 = 'aquamarine';
let bg = `${color1}-${color2}`;
function gradient() {
    color2 = 'lavender';
    console.log(bg);
}
gradient();  // red-aquamarine

解決方法:在使用變數的層級賦值

:root {
  --color1: pink;
  --color2: aquamarine;
}

.gradient {
  --bg: linear-gradient(to right, var(--color1), var(--color2));
  background: var(--bg);
}

.theme-2 {
  --color2: lavender;
}

—-bg變數的賦值放到.gradient層級中,—-color2變數就生效了:

需要合理使用

:root {
  --prop1: lol;
  --prop2: var(--prop1) var(--prop1);
  --prop3: var(--prop2) var(--prop2);
  --prop4: var(--prop3) var(--prop3);
  ...
    --prop30: var(--prop29) var(--prop29);
}

如果無限制地渲染和執行,上面的程式碼段會導致瀏覽器嘗試建立大約十億個—-prop1值('lol')的例項,這足以讓大多數系統記憶體不足。這個例子在2019年的時候使用32gb RAM的MacBook Pro進行測試,30秒執行後Safari停止響應。那時候所有WebKit核心的瀏覽器都容易通過CSS變數受到攻擊,現在的瀏覽器已對該情況做相應的處理,被變數引用賦值超過65536次的變數會被當作無效值處理。

實戰演練(舉個?)

我們可以在不同場景下妙用CSS變數的作用域這一特性,以下例子中演示瞭如何使用CSS變數實現一個可切換主題的開關。

https://codepen.io/bobolovecat/pen/vYZJPop

我們建立了2個作用域範圍,分別是代表明亮主題的.theme-bright和暗黑主題的.theme-dark。在這兩個區域性作用域中使用相同的變數名,各個變數只對主題相應的模組生效。

小結

利用CSS變數的作用域特性和var()的變數回退,有助於我們設計一個程式碼整潔、模組化的系統。但也要注意作用域層級和合理運用CSS變數賦值,避免程式碼結構過於複雜以及影響頁面渲染效能。

參考

The Big Gotcha With Custom Properties
A Complete Guide to Custom Properties
Understanding the Scope in CSS Variables - Part 2 - Webkul Blog

相關文章