為什麼 Vue3 選擇了 CSS 變數

Gopal發表於2021-01-19

為什麼 Vue3 選擇了 CSS 變數

Vue 3 新增了一條實驗性的功能——「單檔案元件狀態驅動的 CSS 變數」

看到這個,我腦子裡有以下的疑問?

  • CSS 變數是什麼?
  • Sass/Less 中不是有變數的定義麼,為什麼還需要使用 CSS 變數?
  • 現有的 Vue 不是通過 :style 的方式定義去動態繫結 CSS,那 CSS 變數和這種方式有什麼區別?
  • Vue 3 做了哪些操作,讓 SFC (單檔案元件)能更好的使用 CSS 變數

以下對這些問題進行探討

CSS 變數基礎

CSS 變數並不是某個框架的產物,而是 CSS 作者定義的一個標準規範

CSS 變數又稱為 CSS 自定義屬性,它包含的值可以在整個文件中重複使用。由自定義屬性標記設定值(比如: --main-color: black;),由 var() 函式來獲取值(比如: color:  var(--main-color);

為什麼選擇兩根連詞線(--)表示? 因為變數 ?Sass 用掉了,@Less 用掉了。為了不產生衝突,官方的 CSS 變數就改用兩根連詞線了

CSS 變數一個簡單例子如下,CSS 變數基礎演示地址

<div class="parent">
  I am Parent
  <div class="child">
    I am Child
  </div>
</div>
.parent {
  /*  變數的作用域就是它所在的選擇器的有效範圍,所以.parent 讀取不到 child 中的變數  */
  color: var(--body-child);
  /*  定義變數  */
  --parent-color: blue;
}
.child {
  /*  通過 var 讀取變數  */
  color: var(--parent-color);
  --child-color: green;
}

結果展示

我們現在 .parent 中定義變數 --parent-color: blue;,在 .child 中使用 color: var(--parent-color);

需要注意的是,變數的作用域就是它所在的選擇器的有效範圍,比如 .child 中定義的 --child-color: green;, 在 .parent 讀取不到的,只針對 .child 元素下的元素有效

如果希望能夠在 HTML 文件中都能訪問到,則可以定義在類 :root

除了基礎的使用,還有以下幾點需要注意

  • CSS 變數的命名是對大小寫敏感的,也就是 --myColor--mycolor 是不一樣的
  • var() 引數可以使用第二個引數設定預設值,當該變數無效的時候,就會使用這個預設值
  • CSS 變數提供了 JavaScript 與 CSS 通訊的一種途徑,在 JS 中我們可以操作 CSS,跟操作普通的 CSS 屬性是一樣的
// 獲取一個 Dom 節點上的 CSS 變數
element.style.getPropertyValue("--my-var");

// 獲取任意 Dom 節點上的 CSS 變數
getComputedStyle(element).getPropertyValue("--my-var");

// 修改一個 Dom 節點上的 CSS 變數
element.style.setProperty("--my-var", jsVar + 4);

這裡就演示了最簡單的使用,具體可以檢視 MDN 文件

在 Vue 2 中使用CSS 變數

上面說了,CSS 變數並不是什麼某個框架的產物,而是原生 CSS 的標準規範。那麼在 Vue 2 中直接使用 CSS 變數肯定可以的,並沒什麼約束。

關鍵是我們怎麼讓 Vue 元件中的狀態同步到 CSS 變數中,其實也很簡單,通過 Style 繫結 即可。Vue 2 演示地址

<template>
  <!-- 如果要該元件都可以使用,則必須放置在根元素下 -->
  <div class="hello" :style="styleVar">
    <div class="child-1">I am Child 1</div>
    <div class="child-2">I am Child 2</div>
    <div @click="onClick">Change Red TO Blue</div>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  data() {
    return {
      styleVar: {
        "--colorBlue": "blue",
        "--colorRed": "red",
        "--fontSize": "30px",
        "--fontSizeTest": "30px",
      },
    };
  },
  methods: {
    onClick() {
      this.styleVar["--fontSizeTest"] = "40px";
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.child-1 {
  color: var(--colorBlue);
  font-size: var(--fontSize);
}
.child-2 {
  color: var(--colorRed);
  font-size: var(--fontSizeTest);
}
</style>

結果:

我們只需要在元件的根元素中設定 :style="styleVar"(如果要該元件都可以使用,則必須放置在根元素下),就可以在 Vue 2.x 中實現元件中的狀態和 CSS 值的繫結,而且這種繫結關係是響應式的,比如我定義一個方法,改變 font-size 的值,是可以實時更新的

onClick() {
  this.styleVar["--fontSizeTest"] = "40px";
},

效果演示:

:style VS CSS 變數

這裡有個問題,現有的 Vue 可以通過 :style 的方式定義去動態繫結 CSS,比如我可以直接在上面的 .child-1中做如下繫結,效果跟上面是一致的。

<div class="child-1" :style="{ color: 'blue', fontSize: '30px' }">
  I am Child 1
</div>

那我為什麼還要使用 CSS 變數?這樣大費周章是否真有意義?

我總結有如下兩個原因:

原因一:
複雜的網站都會有大量的 CSS 程式碼,通常也會有許多重複的值。當元件中的一個狀態被幾十個地方用到時,那麼你可能需要繫結很多個 :style。一來程式碼會顯得可讀性不強,二來效能上應該是比原生的要差,畢竟要將更改經過 Vue 的指令繫結到每一個元素上(這一點暫未驗證)

通過 CSS 變數,就可以直接通過在元件的根元素設定變數,在元件內部 <style> 中直接使用即可

原因二:偽元素的使用
如果直接使用 :style 我們無法設定偽元素的樣式,而 CSS 變數就可以

p::first-line {
  color: var(--theme-secondary-color);
}

在 Vue 3 中使用 CSS 變數

雖然 Vue 2.x 中可以使用 CSS 變數,但需要通過樣式繫結的方式傳入,似乎沒那麼優雅,所以 Vue 3 中做了一些優化

新增 vars 繫結

<template>
  <div class="text">hello</div>
</template>

<script>
export default {
  data() {
    return {
      color: "red",
    };
  },
};
</script>

<style vars="{ color }">
.text {
  color: var(--color);
}
</style>

Vue 3 中的 SFC 中,style 標籤支援 vars 繫結,該引數接受物件鍵值對方式注入 CSS 變數,如上所示 <style vars="{ color }">。可以效果可以看 Vue 3演示地址

這些變數會直接繫結到元件的根元素上,上面的例子中,最後的渲染結果如下:

<div style="--color:red" class="text">hello</div>

和 <style scoped> 一起使用

vars<style scoped> 一起使用時,所應用的 CSS 變數將以元件的 Scoped id 作為字首,訪問的時候也會自動加上 Scoped id

比如,我們書寫如下:

<style scoped vars="{ color }">
h1 {
  color: var(--color);
}
</style>

則編譯過後,變成

h1 {
  color: var(--6b53742-color);
}

假如我們這種情況下想訪問的是全域性的 CSS 變數呢?也就是我們不希望加上 Scoped Id,那麼要書寫類似如下:

<style scoped vars="{ color }">
h1 {
  color: var(--color);
  font-size: var(--global:fontSize);
}
</style>

這樣會編譯成如下結果:

h1 {
  color: var(--6b53742-color);
  font-size: var(--fontSize);
}

Less/Sass 中的變數 VS CSS 變數

我理解最重要的一點,就是 CSS 變數可以跟 JavaScript 更好的通訊,相當於 CSSJavaScript 的橋樑。在 Vue 中這一點還是體現得挺明顯的

另外來看一個切換主題的例子,如果我們用 Sass 變數,如下:

$color-primary: blue;
$color-text: black;
$color-bg: white;
/* invert */
$color-primary-invert: red;
$color-text-invert: white;
$color-bg-invert: black;

.component {
  color: $color-text;
  background-color: $color-bg;

  a {
    color: $color-primary;
  }
}

.component--dark {
  color: $color-text-invert;
  background-color: $color-bg-invert;

  a {
    color: $color-primary-invert;
  }
}

我們有兩個主題,一個是普通的主題,一個暗黑模式的(dark)。注意,在暗黑模式中,我們需要新的顏色變數去更新舊的顏色變數。假如這種設定非常多的時候,我們會很苦惱。

CSS 變數設定的話

:root, [data-theme="default"] {
  --color-primary: blue;
  /* color contrasts */
  --color-bg: white;
  --color-contrast-lower: hsl(0, 0%, 95%);
  --color-contrast-low: hsl(240, 1%, 83%);
  --color-contrast-medium: hsl(240, 1%, 48%);
  --color-contrast-high: hsl(240, 4%, 20%);
  --color-contrast-higher: black;
}

[data-theme] {
  background-color: var(--color-bg);
  color: var(--color-contrast-high);
}

[data-theme="dark"] {
  --color-primary: red;
  /* color contrasts */
  --color-bg: black;
  --color-contrast-lower: hsl(240, 6%, 15%);
  --color-contrast-low: hsl(252, 4%, 25%);
  --color-contrast-medium: hsl(240, 1%, 57%);
  --color-contrast-high: hsl(0, 0%, 89%);
  --color-contrast-higher: white;
}

這種情況下,我們不需要額外定義一個顏色變數,因為我們只需要設定CSS 變數為正確的值即可

之所以會有以上用法上的不同,我理解是 SASS 變數是編譯時,也就是說前處理器在向瀏覽器輸出前已經解析完畢,而瀏覽器對 CSS 變數解析是執行時的

另外前處理器和 CSS 變數並不衝突,它們結合可以更好的提升我們的開體驗

缺點——瀏覽器相容性問題

CSS 變數目前的支援度並非特別好,IE 目前全部都是不支援的,但終上所述,依舊看好它的未來

對它的相容性進行處理,我們也可以進行如下書寫:

a {
  color: #7F583F;
  color: var(--primary);
}

總結

最後總結一下文章開頭提出的幾個問題:

  • CSS 變數是什麼?

    • CSS 變數又稱為 CSS 自定義屬性,它包含的值可以在整個文件中重複使用
  • 現有的 Vue 不是通過 :style 的方式定義去動態繫結 CSS,那 CSS 變數和它又有什麼區別呢?

    • 複雜的網站都會有大量的 CSS 程式碼,通常也會有許多重複的值
    • 偽元素的使用
  • Vue 3 做了哪些操作,讓 SFC (單檔案元件)能更好的使用 CSS 變數

    • 新增 vars 繫結
    • <style scoped> 一起使用,增強作用域功能
  • Sass/Less 中不是有變數的定義了麼,為什麼還要使用 CSS 變數?

    • CSS 變數可以跟 JavaScript 更好的通訊,相當於 CSSJavaScript 的橋樑
    • SASS 變數是編譯時,CSS 變數是執行時

參考

相關文章