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 给了用户太多选择,让用户无所适从。
参考: