Vue3 style CSS 變數注入

guangzan 發表於 2021-07-21
Vue CSS

摘要

在單檔案元件樣式中支援使用元件狀態驅動的 CSS 變數( CSS 自定義屬性)。

基礎示例

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

<script>
  export default {
    data() {
      return {
        color: 'red',
        font: {
          size: '2em',
        },
      }
    },
  }
</script>

<style>
  .text {
    color: v-bind (color);

    /* expressions (wrap in quotes) */
    font-size: v-bind ('font.size');
  }
</style>

動機

Vue SFC 樣式提供了直接的 CSS 搭配和封裝,但它是純粹的靜態的 —— 這意味著到目前為止,我們沒有能力在執行時根據元件的狀態動態更新樣式。

現在,隨著大多數現代瀏覽器支援原生 CSS 變數,我們可以利用它來輕鬆連線元件的狀態和樣式。

設計細節

SFC 中的標籤現在支援一個自定義 CSS 函式 v-bind

<!-- in Vue SFC -->
<style>
  .text {
    color: v-bind (color);
  }
</style>

正如預期的那樣,這將把宣告的值繫結到元件狀態的屬性上,reactively.color color

該函式內部可以支援任意的 JavaScript 表示式,但由於 JavaScript 表示式可能包含在 CSS 識別符號中無效的字元,因此在大多數情況下需要用引號來包裹它們:v-bind

.text {
  font-size: v-bind ('theme.font.size');
}

當檢測到這種 CSS 變數時,SFC 編譯器將執行以下操作:

  1. 重寫到一個帶有雜湊變數名稱的本機。上面的內容將被改寫為:v-bind () var ()

    .text {
      color: var (--6b53742-color);
      font-size: var (--6b53742-theme_font_size);
    }
    

    請注意,hash 將應用於所有情況,無論標籤是否有範圍。這意味著注入的 CSS 變數不會意外地洩漏到子元件中。

  2. 相應的變數將作為內聯樣式被注入到元件的根元素中。對於上面的例子,最終渲染的 DOM 將看起來像這樣:

    <div style="--6b53742-color:red;--6b53742-theme_font_size:2em;" class="text">
      hello
    </div>
    

    注入是響應式的 ——所以如果元件的屬性發生變化,注入的 CSS 變數將被相應地更新。這種更新是獨立於元件的模板更新的,所以對一個純 CSS 的響應式屬性的改變不會觸發模板的重新渲染。

編譯細節

  • 為了注入 CSS 變數,編譯器需要生成並注入如下程式碼到元件的 setup ()

    import { useCssVars } from 'vue'
    
    export default {
      setup() {
        //...
        useCssVars(_ctx => ({
          color: _ctx.color,
          theme_font_size: _ctx.theme.font.size,
        }))
      },
    }
    

    ... 這裡,執行時幫助器設定了一個將變數響應性地應用到 DOM.useCssVars watchEffect 上。

  • 該編譯策略要求指令碼編譯時首先對標籤內容進行簡單的重碼解析,以確定要暴露的變數列表。然而,這個解析階段不會像基於 AST 的完整解析 <style> 那樣耗費開銷。

  • 在生產中,變數名可以被進一步 hash,以減少 CSS 的佔用。

    .text {
      color: var (--x3b2fs2);
      font-size: var (--29fh29g);
    }
    

    相應的生成的 JavaScript 程式碼將相應地使用相同的雜湊值。

採用策略

這是一個完全向後相容的新功能。然而,我們應該明確指出,它依賴於本地的 CSS 變數,所以使用者需要了解瀏覽器的支援範圍。

實踐

在 script 中宣告兩個響應式的屬性,分別是 wallpaperBlurwallpaperMaskwallpaperBlur 表示桌布的模糊程度, wallpaperMask 表示遮罩的透明度。通過 v-bind 將它們應用到 style,這意味著當我們在 script 中改變這兩個值時,樣式會響應更改。

// script
const wallpaperBlur = ref('0px')
const wallpaperMask = ref('rgba(0, 0, 0, 0)')
// style
.wallpaper {
  filter: blur(v-bind(wallpaperBlur));
  bottom: calc(v-bind(wallpaperBlur) * -2);
  left: calc(v-bind(wallpaperBlur) * -2);
  right: calc(v-bind(wallpaperBlur) * -2);
  top: calc(v-bind(wallpaperBlur) * -2);
  .wallpaper-image {
    transition: background-image 0.6s, background-color 0.4s;
  }
  .wallpaper-mask {
    background-color: v-bind(wallpaperMask);
  }
}

image

提示

繫結恰當的屬性

在上面的例子中,你可能想到到更改遮罩的透明度僅需要宣告一個 0-1 的數字,之後在 style 中這樣寫:

.wallpaper-mask {
  background-color: rgba(0, 0, 0, v-bind(wallpaperMask));
}

上文已經提到在編譯階段會將 style 中的 v-bind 改寫為 CSS 變數的形式,上面的程式碼會被改寫為這樣:

.wallpaper-mask {
  background-color: rgba(0, 0, 0, var (--[hash]-wallpaper_mask));
}

rgba(0, 0, 0, var (--[hash]-wallpaper_mask)) 在 CSS 中是無法被解析的。所以這就是為什麼將 wallpaperMask 的初始值宣告為 rgba(0, 0, 0, 0) 的原因。這是需要十分注意的一點,CSS 中還有許多類似的情況。

注意 style 的更新

在設計細節中提到相應的變數將作為內聯樣式被注入到元件的根元素中。最終渲染的 DOM 將看起來像這樣:

<div style="--6b53742-color:red;--6b53742-theme_font_size:2em;"></div>

當你在 <script> 中改變 <style> 中繫結的屬性時,內斂樣式中的 CSS 變數將會響應更改。但是,並不能單獨更新內斂樣式其中的一個 CSS 變數,這意味著更新一個元件中的任意一個“動態樣式”,都將引起根元件中的內斂樣式全部更新。當 style 屬性的值包含大量 CSS 變數時,你需要考慮重新組織元件。因為編譯生成的 CSS 變數都將作為內聯樣式被注入到元件的根元素中,我們無法控制這種行為,將一個引起更新的 CSS 變數和其他 CSS 變數解耦。

試想這種情況, style 中編譯生成的 CSS 變數中包含一個其值為龐大的 base64 的 CSS 變數。當更新該元件中其他 CSS 變數時,整個 style 都將更新,這將帶來額外的硬體開銷。我們需要將這個生成 base64 CSS 變數的元件單獨抽離,以使該 CSS 變數注入到該元件的根元素,不受其他 CSS 變數更新影響。

參考資料