摘要
在單文件組件樣式中支持使用組件狀態驅動的 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 編譯器將執行以下操作:
-
重寫到一個帶有哈希變量名稱的本機。上面的內容將被改寫為:
v-bind ()
var ()
.text { color: var (--6b53742-color); font-size: var (--6b53742-theme_font_size); }
請注意,hash 將應用於所有情況,無論標簽是否有范圍。這意味着注入的 CSS 變量不會意外地泄漏到子組件中。
-
相應的變量將作為內聯樣式被注入到組件的根元素中。對於上面的例子,最終渲染的 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 中聲明兩個響應式的屬性,分別是 wallpaperBlur
和 wallpaperMask
。wallpaperBlur
表示壁紙的模糊程度, 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);
}
}
提示
綁定恰當的屬性
在上面的例子中,你可能想到到更改遮罩的透明度僅需要聲明一個 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 變量更新影響。