CSS 變量能夠跟 JavaScript 更好的通訊,CSS 變量是運行時。
經過本文你會認識並理解如下概念:css
在 SFC Style Variables 提案中介紹到, Vue SFC 樣式提供了簡單的 CSS 組合和封裝,但它是純靜態的 — 這意味着到目前爲止咱們尚未能力在運行時根據組件的狀態動態更新樣式。html
如今大多數現代瀏覽器都支持原生 CSS 變量,咱們能夠利用它輕鬆鏈接組件的狀態和樣式。vue
Vue 單文件組件 (SFC) 規範 中介紹到,.vue
文件是一個自定義的文件類型,用類 HTML 語法描述一個 Vue 組件。每一個 .vue
文件包含三種類型的頂級語言塊 <template>
、<script>
和 <style>
,還容許添加可選的自定義塊。node
Style 語言塊:react
/\.css$/
。一個 .vue
文件能夠包含多個 <style>
標籤。webpack
<style> 標籤能夠有 scoped 或者 module 屬性 (查看 scoped CSS 和 CSS Modules) 以幫助你將樣式封裝到當前組件。具備不一樣封裝模式的多個 <style> 標籤能夠在同一個組件中混合使用。git
.css
文件 (或經過它的 lang
特性指定的擴展名) 的 webpack 規則都將會運用到這個 <style>
塊的內容中。vue-loader
會解析文件,提取語言塊,若有必要會經過其它 loader 處理,最後將他們組裝成一個 ES Module,它的默認導出是一個 Vue.js 組件選項的對象。github
Stlye 模塊中能夠使用 lang
屬性,指定 CSS 預處理語言(sass、less、stylus),以下:web
/* lang 屬性指定擴展名 */ <style lang="sass"> /* write Sass! </style>
還能夠使用 src
屬性,引入外部樣式資源:express
<style src="./style.css"></style> /* 從 npm 依賴中引入資源 */ <style src="todomvc-app-css/index.css">
更多 Vue 單文件組件 (SFC) 規範 介紹。
CSS 變量是 CSS 做者定義的標準規範。
CSS 變量又稱爲 CSS 自定義屬性,它包含的值能夠在整個文檔中重複使用,示例以下:
/* :root 僞類表明 HTML 文檔的根元素,是存放自定義屬性的最佳位置。*/ :root { /* --text-color 爲自定義屬性 */ --text-color: #000000; } p { /* 使用時,須要使用 var() 函數並傳入自定義屬性。 */ color: var( --text-color ); font-size: 16px; } h1 { color: var( --text-color ); font-size: 42px; }
在使用 CSS 自定義屬性以前,咱們須要先聲明自定義屬性,屬性名須要以兩個減號(--
)開始,屬性值則能夠是任何有效的 CSS 值。和其餘屬性同樣,自定義屬性也是寫在規則集以內的,以下:
element { --main-bg-color: brown; }
規則集所指定的選擇器,定義了自定義屬性的可見做用域。一般的最佳實踐是定義在根僞類 :root
下,這樣就能夠在HTML文檔的任何地方訪問到它了。
:root { --main-bg-color: brown; }
注意:自定義屬性名是大小寫敏感的,--my-color
和--My-color
會被認爲是兩個不一樣的自定義屬性。
如前所述,使用一個局部變量時用 var()
) 函數包裹以表示一個合法的屬性值:
element { background-color: var(--main-bg-color); }
:root { --main-bg-color: brown; } .one { color: white; background-color: var(--main-bg-color); margin: 10px; width: 50px; height: 50px; display: inline-block; }
關於 CSS 自定義屬性的繼承性、備用值等更多介紹,請參考使用CSS自定義屬性(變量)。
sfc-style-variables 主要概述中指出,此提案支持單文件組件狀態驅動的 CSS 變量注入到單文件組件樣式中。
<template> <div class="text">hello</div> </template> <script> export default { data() { return { color: 'red' } } } </script> <style vars="{ color }"> .text { color: var(--color); } </style>
因爲 Vue SFC 樣式提供了簡單的 CSS 搭配和封裝,但它是純靜態的 — 這意味着到目前爲止咱們尚未能力在運行時根據組件的狀態動態更新樣式。
如今大多數現代瀏覽器都支持原生 CSS 變量,咱們能夠利用它輕鬆鏈接組件的狀態和樣式。
在此提案設計中(舊版),Vue SFC Style 支持 vars 綁定,它接受一個 key/values 表達式做爲 CSS變量注入。它與 <template>
中的表達式在相同的上下文中進行計算。
變量將做爲內聯樣式應用於組件的根元素。在上面的示例中,給定一個值爲 {color:'red'}
的 vars
綁定,呈現的 HTML 將是:
<div style="--color:red" class="text">hello</div>
scoped
模式下當在 scoped
模式下使用,須要確保 CSS 變量不會泄漏到後代組件或不當心將 CSS 變量遮蔽到 DOM 樹的更高層。應用的 CSS 變量將以組件的做用域 ID 爲前綴:
<div style="--6b53742-color:red" class="text">hello</div>
請注意,當 scoped 和 vars 同時存在時,全部 CSS 變量都被視爲本地變量。
在這種狀況下,使用全局 CSS 變量,須要使用 global:
前綴:
<style scoped vars="{ color }"> h1 { color: var(--color); font-size: var(--global:fontSize); } </style>
vars
以公開能夠使用的變量。scoped/non-scoped
模式下的不一樣行爲。non-scoped
模式下,CSS 變量會泄漏到子組件中。scoped
模式下,使用在組件外部聲明的普通 CSS 變量須要 global:
前綴。(一般 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>
v-bind()
進行推斷);scoped/non-scoped
模式下的相同行爲;示例不詳細介紹,請結合註釋進行理解。
示例中包含:
script setup
;var()
;v-bind
。<template> <div class="root"> <span class="test" @click="changeColor"> Vue GoldenLayout</span> <div> </template> // script setup <script lang="ts" setup> import { defineComponent, reactive } from "vue"; // 將 css 樣式單獨抽離 import { style } from "../styles/vlogo"; const css = reactive({ ...style }); // 點擊,修改組件的樣式 const changeColor = () => (css.color = "yellow"); </script> <style> .root { // 原生 css 變量的定義 --custom-color: v-bind("css.background"); } .test { // 使用 v-bind 進行狀態驅動 color: v-bind("css.color"); // 使用 var() background: var(--custom-color); } </style>
最終呈現的效果以及編譯後的 HTML 、CSS:
注:在 vue3 中使用 CSS 變量注入,推薦的風格是將 CSS 樣式單獨抽離出去。
咱們在上文 SFC Style 簡單介紹中瞭解到,vue-loader
會解析 .vue
文件,並提取語言塊,若有必要會經過其它 loader 處理,最後將他們組裝成一個 ES Module,它的默認導出是一個 Vue.js 組件選項的對象。
若是在 <style>
中經過 lang
屬性,使用其餘 CSS 預處理語言(less
、sass
)等,則會匹配構建工具(webpack、vite)所配置的 loader
進行特定處理。
/*packages/compiler-sfc/sfc/stylePreprocessors.ts */ // .scss/.sass processor const scss: StylePreprocessor = (source, map, options, load = require) => {...} const sass: StylePreprocessor = (source, map, options, load) => {...} // .less const less: StylePreprocessor = (source, map, options, load = require) => {...} // .styl const styl: StylePreprocessor = (source, map, options, load = require) => {...}
Vue3 SFC Style 中的部分編譯主要是由 postcss
完成的,在 Vue 源碼中對應着 packages/compiler-sfc/sfc/compileStyle.ts
中的 doCompileStyle()
方法。
這裏,咱們看一下其針對 <style>
動態變量注入的編譯處理,對應的代碼(省略代碼):
export function doCompileStyle( options: SFCAsyncStyleCompileOptions ): SFCStyleCompileResults | Promise<SFCStyleCompileResults> { const { ... id, ... } = options ... const plugins = (postcssPlugins || []).slice() plugins.unshift(cssVarsPlugin({ id: shortId, isProd })) ... }
能夠看到,在使用 postcss
編譯 <style>
以前會加入 cssVarsPlugin
插件,並給 cssVarsPlugin
傳入 shortId
(即 scopedId
替換掉 data-v
內的結果)和 isProd
(是否處於生產環境)。
cssVarsPlugin
則是使用了 postcss
插件提供的 Declaration
方法,來訪問 <style>
中聲明的全部 CSS 屬性的值,每次訪問經過正則來匹配 v-bind
指令的內容,而後再使用 replace()
方法將該屬性值替換爲 var(--xxxx-xx)
,表如今上面這個例子會是這樣:
cssVarsPlugin
插件的定義:
const cssVarRE = /\bv-bind\(\s*(?:'([^']+)'|"([^"]+)"|([^'"][^)]*))\s*\)/g const cssVarsPlugin: PluginCreator<CssVarsPluginOptions> = opts => { const { id, isProd } = opts! return { postcssPlugin: 'vue-sfc-vars', Declaration(decl) { // rewrite CSS variables if (cssVarRE.test(decl.value)) { decl.value = decl.value.replace(cssVarRE, (_, $1, $2, $3) => { return `var(--${genVarName(id, $1 || $2 || $3, isProd)})` }) } } } }
這裏 CSS var()
的變量名( --
以後的內容)是由 genVarName()
方法生成,它會根據 isProd
爲 true
或 false
生成不一樣的值:
function genVarName(id: string, raw: string, isProd: boolean): string { if (isProd) { return hash(id + raw) } else { return `${id}-${raw.replace(/([^\w-])/g, '_')}` } }
以上只是對 SFC Style 塊的相關處理的部分解讀,關於更完整的源碼解析請參考 Vue 3 的 SFC Style CSS Variable Injection 提案實現的背後 。
loader
。最後,想吐槽的一點是,從 Vue2 到 Vue3 的轉變是思想上的轉變,Vue 3 給了用戶太多選擇,讓用戶無所適從。
參考: