跟我一起全面了解一下CSS变量
什么是css变量?
CSS变量,即CSS variable。官方的名称是 级联变量的CSS自定义属性 ,即 CSS custom properties for cascading variables 。类似于sass,less等预处理器的变量。css变量同样具备声明,引用,以及作用域等变量特性。
如果你学过其他编程语言,应该很熟悉变量的概念。变量用于存储和更新你的程序所需要的值,以便使它运行。在一些命令式编程语言中,像Java、C++亦或是JavaScript,通过变量我们能够跟踪某些状态。变量是一种符号,关联着一个特定的值,变量的值能随着时间的推移而改变。不同于其它编程语言,在像CSS这种声明式语言中,随着时间而改变的值并不存在,也就没有所谓变量的概念了。
CSS 引入了一种层级变量的概念,从而能够从容应对可维护性的挑战。这就会使得在整个 CSS tree 中都可以象征性的引用一个变量。
CSS变量能解决什么问题?
在构建大型站点时,开发者通常会面对可维护性的挑战。在这些网页中, 所使用的 CSS 的数量是非常庞大的,并且在许多场合大量的信息会重复使用。例如,在网页中维护一个配色方案,意味着一些颜色在CSS文件中多次出现,并被重复使用。当你修改配色方案时,不论是调整某个颜色或完全修改整个配色,都会成为一个复杂的问题,不容出错,而单纯查找替换是远远不够的。
当然,目前解决维护大型站点css文档带所来的困扰,是有非常成熟的解决方案的。比如,可以采用sass,less,stylus等预处理器。
但是,CSS变量有着天然优势。不同于预处理器,必须最终编译成普通的css代码,CSS变量可以被浏览器识别解析,甚至可以用javascript访问或重新赋值。比如,在线修改一个网站主题色,只需修改相应变量即可实现。某种意义上,这为css开启了一扇通向各种可能性的大门。
除此之外,w3c规范是这样描述CSS变量的
使用CSS变量,给看似随机的值加上富有信息的名字,从而使得大文件更容易阅读和编辑,更少出错。因为,你只需要在自定义属性中改变一次值,所有应用了这个变量的地方都会自动跟着一起改变。
可以自定义变量名称,使变量更有语义,更有可读性。毕竟primary-color比#0055aa更容易理解,特别是同样的颜色出现在不同的文件中的时候,颜色之间的关联关系更加直观。
总结:
1,提高大型系统文档的可维护性;
2,提高系统代码的可读性;
3,可以在浏览器中实时修改生效;
CSS变量的声明
CSS变量也叫CSS自定义属。即为属性,同样是由合法的标识和合法的值组成。不同于普通属性,自定义属性有"--"前缀,早期的标准也有采用"var-"作为前缀。之所以会采用"--",是因为通常变量声明常用的前缀"$"或者"@"已经被预处理器sass和Less占用了。为了保证css变量和预处理器变量不产生冲突,所以采用了"--"。
看下面例子
声明变量
:root{
--primary-color: green;
}
引用变量,
.primary-button{
background: var(--primary-color)
}
- 变量声明中,属性值不能直接进行数学运算,需要借助calc()函数
例如
--font-size:12px *2; /**错误的方式**/
--font-size:calc(12px*2);/**正确的方式**/
- CSS变量可以用在任意css选择器中,甚至可以用在style以及@media中
各种选择器中使用
:root{
--primary-color:green;
div{
--normal-size:14px;
#con{
--local-color:blue;
color:var(--primary-color);
#con .box{
--content:'normal box';
color:var(--local-color)
.box:after{
content:var(--content);
}
HTML的style中使用
<body style="--color:red;">
<span style="color:var(--color)">CSS变量</span>
</div>
@media查询中使用
:root{
--column-size:4;
@media screen and (min-width: 768px) {
:root{
--column-size: 3;
}
- CSS变量不同于预处理器变量,css变量只能用于属性值
例如,sass中我们可以这样声明:
$name:color;
$primary-color:green;
$i:1;
.box{
#{$name}:$primary-color;
.child-#{$i}{
color:$primary-color;
}
但是,换成CSS变量,下面这种用法会抛出不合法的属性名异常
:root{
--name:color;
--primary-color:green;
.box{
var(--name):var(--primary-color);
}
- CSS变量名称区分大小写,--primary-color 不同于 --Primary-Color
:root{
--primary-color:red;
--Primary-Color:green;
.one{
color:var(--primary-color); /**red**/
.two{
color:var(--Primary-Color); /**green**/
}
- 属性名可以包含数字,字母,以及下划线或者短横线,也可以是中文,日文或者韩文
body {
--深蓝: #369;
background-color: var(--深蓝);
}
注意,属性名不能包含$,[,^,(,%等字符
变量的引用
CSS变量的引用,需要用到var( <custom-property-name> , <declaration-value> ? )函数,
css其实有很多有用的函数, 这里
var函数的第一个参数是要替换的自定义属性的名称。函数的可选第二个参数用作备用值。如果第一个参数引用的自定义属性无效,则该函数将使用第二个值。
例如
:root{
--bg-color: green;
.box{
background-color:var(--bg-color, red);
}
- 备用值declaration-value中允许使用逗号,var()函数第一个逗号以后的部分作为整体返回,用作var()的备用值。
例如
.box{
background-image:var(--bg-img, url(./one.png), url(./two.png));
}
上面例子,--bg-img变量不存在,所以计算结果是
background-image: url(./one.png), url(./two.png)
- var()函数只解析第一个参数引用的变量
看下面例子,box的字体颜色是什么呢?
body{
color:red;
:root{
--two-color:blue;
.box{
color: var(--one-color, --two-color);
}
答案是red。因为var函数只会解析第一个参数,剩余的参数会整体返回作为备用值,并且不会解析。因为--two-color的值是一个无效的颜色值。所以最终的颜色是从body继承的red。备用值如果是变量,同样需要var()函数去引。这意味着var函数是可以嵌套的。
正确的书写方式
.box{
color: var(--one-color, var(--two-color));
}
当然--two-color同样可以有自己的备用值
.box{
color: var(--one-color, var(--two-color, #0055aa));
}
由上例可知,var()函数可以用在var()的参数当中。
另外,需要注意的是,备用值可以包含任何字符,但是部分有特殊含义的字符除外,例如换行符、不匹配的右括号(如)、]或}
)、感叹号以及顶层分号(不被任何非var ()
的括号包裹的分号,例如var(--bg-color, --bs ; color)
是不合法的,而var(--bg-color, --value ( bs;color ) )
是合法的)。
例如,下面的例子空格作为分隔符是合法的
:root{
--border-one:1px;
.box{
border-width: var(--border-two, 1px 2px 3px 4px);
}
- CSS变量在CSS变量声明中引用
例如
:root{
--primary-color:green;
--button-default-color:var(--primary-color);
--normal-font-size:16px;
--button-normal-font-size:var(--normal-font-size);
}
注意:尽量避免循环引用
- CSS变量在拼接计算中引用
如果变量都是字符串,可以直接拼接
:root{
--text-one:'hello';
--text-two:'world'
.box:after{
content: var(--text-one)' 'var(--text-two);
}
变量和单位不能直接拼接
:root{
--size-one:14;
.box{
font-size:var(--size-one)px; /**无效**/
}
计算结果将是
font-size: 14 px;
由上可知,变量和单位直接拼接会多出一个空格,正确的拼接方式是借用calc()函数,进行计算
font-size:calc(var(--size-one)*1px);
当然,calc也可以用作CSS变量值当中
--size-two:calc(20px * 2)
变量值的类型
变量值可以是有效的css属性值,比如颜色,尺寸,定位,角度,字符串,数字等,也可以是多个字段的组合,甚至可以是javascript语句
例如
:root{
--main-color: #4d4e53;
--main-bg: rgb(255, 255, 255);
--logo-border-color: rebeccapurple;
--header-height: 68px;
--content-padding: 10px 20px;
--base-line-height: 1.428571429;
--transition-duration: .35s;
--external-link: "external link";
--margin-top: calc(2vh + 20px);
/* Valid CSS custom properties can be reused later in, say, JavaScript. */
--foo: if(x > 5) this.width = 10;
}
上面代码中,--foo的值在 CSS 里面是无效值,但是可以被 JavaScript 读取。这意味着,可以把样式设置写在 CSS 变量中,让 JavaScript 读取。
这里有一个比较重要的概念需要理解和区分。在传统的CSS概念里,属性的有效性(validity)对于自定义属性来讲,并不适用。当自定义属性被解析,浏览器不知道哪里为调用到它们,所以几乎所有的值都是有效的。
不幸的是,这些有效值能通过var()函数操作符来调用,即使在当前上文没有意义。属性和自定义的值会导致无效的CSS声明,所以有了 计算时有效(valid at computed time) 的概念。
CSS变量的作用域和继承
- CSS自定义属性可以被继承,并遵循CSS级联规则
CSS变量,即CSS自定义属性。跟普通属性一样,CSS自定义属性的继承,优先级同样遵循 CSS级联规则 。一个元素上没有定义自定义属性,该自定义属性的值会继承其父元素。按照层叠关系向上查找。最终查找到根元素,也即前面例子中用到的:root选择器所定义的变量。因此,变量的作用域就是它所在的选择器的有效范围
:root{
--primary-color:green;
body{
--primary-color:blue;
.box{
--primary-color:red;
.box__txt{
color:var(--primary-color)
}
html代码
<!DOCTYPE html>
<html lang="en">
<div class="box">
<span class="box__txt">CSS变量</span>
</div>
</body>
</html>
:root中声明的--primary-color,作用域是整个dom树;body中声明的--primary-color,作用域是整个body范围内有效;.box中声明的--primary-color,作用域是其本身及其后代选择器。.box__text容器中并没有声明变量--primary-color,但是会继承父元素中声明的--primary-color。所以最终.box__text容器的字体颜色最终呈现为red。由此,CSS变量的作用域与其选择器有效范围一致。
- 自定义属性优先级支持!important
:root{
--primary-color:green;
body{
--primary-color:blue;
.box{
--primary-color:red!important;
font-size:36px;
.box-1{
--primary-color:orange;
font-size:24px;
.box__txt{
color:var(--primary-color)
}
html代码
<!DOCTYPE html>
<html lang="en">
<div class="box box-1">
<span class="box__txt">CSS变量</span>
</div>
</body>
</html>
依据就近原则,字体大小呈现为24px;依据!important关键字的优先级,字体颜色呈现为red。
思考一下面例子,.box__txt容器字体将是什么颜色?
:root{
--primary-color:green;
color:#333333;
body{
--primary-color:blue;
color:hotpink;
.box{
--primary-color:red;
font-size:36px;
.box-1{
--primary-color:inherit;
font-size:24px;
.box__txt{
color:var(--primary-color)
}
CSS变量的兼容性
除了IE11(它不支持CSS变量),所有主流浏览器都对CSS变量有全面地支持。见 这里
可以使用
@support
命令进行检测是否支持CSS变量
@supports ( (--a: 0)) {
/** supported **/
@supports ( not (--a: 0)) {
/* not supported */
}
对于不支持CSS变量的浏览器,一个变通的方案是使用具有虚拟查询条件(dummy conditional query)的
@supports
代码块
section {
color: gray;
@supports(--css: variables) {
section {
--my-color: blue;
color: var(--my-color, 'blue');
}
javaScript 也可以检测浏览器是否支持 CSS 变量
const isSupported =
window.CSS &&
window.CSS.supports &&
window.CSS.supports('--a', 0);
if (isSupported) {
/* supported */
} else {
/* not supported */
javascript 操作CSS变量
- 可以直接通过JavaScript代码访问CSS变量
getPropertyValue(): 读取变量
root.style.getPropertyValue('--global-color').trim();
注意,上面代码,仅仅读取root.style中声明的变量,并不能读取:root选择器中声明的变量
读取选择器中的变量,需要借助styleSheet或者getComputedStyle
const root = document.documentElement
let _style = getComputedStyle(root)
_style.getPropertyValue('--global-color').trim()
setProperty():设置变量
root.style.setProperty('--global-color','green');
removeProperty(): 删除变量
root.style.removeProperty('--global-color');
上面代码中,setProperty和removeProperty,只是对root.style进行操作,并不能:root选择器起作用。那么是否可以借助getComputedStyle进行操作呢?
const root = document.documentElement
let _style = getComputedStyle(root)
_style.setProperty('--global-color','blue')
_style.removeProperty('--global-color')
执行上面代码,浏览器将会报错:
Uncaught DOMException: Failed to execute 'setProperty' on 'CSSStyleDeclaration':
These styles are computed, and therefore the '--global-color' property is read-only.
因为,getComputedStyle的返回值上的属性是只读的
此外,可以通过操作styleSheet,insertRule方法,重写覆盖变量值。
var style = document.createElement("style");
style.type = 'text/css';
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
sheet = style.sheet || style.styleSheet;
sheet.inserRule(`:root{
--global-color: orange;
}`,0)
缺点很明显,操作比较繁琐,不适用于维护较大的项目;
下面这种方式更适合大型项目
:root.global__style--primary{
--button-color: green;
--button-font-size:14px;
--button-text-color: #fffff;
:root.global__style--warning{
--button-color:orange;
--button-font-size:12px;
--button-text-color:#fffff;
.button{
background-color:var(--button-color);
font-size:var(--button-font-size);
color:var(--button-text-color);
}
通过操作root的className,更新变量
let root = document.documentElement;
root.className = 'global__style--primary'
// or
root.className = 'global__style--warning'
利用CSS变量做一些有趣的事情
- 实现的简单鼠标跟随
JS code
let root = document.documentElement;
root.addEventListener("mousemove", e => {
root.style.setProperty('--mouse-x', e.clientX + "px");
root.style.setProperty('--mouse-y', e.clientY + "px");
css code
.mover {
width: 50px;
height: 50px;
background: red;
position: absolute;
left: var(--mouse-x);
top: var(--mouse-y);
}
html code
<div class="mover"></div>
查看效果, 这里
- 根据背景色,设置合适的文字颜色
首先看一下,通过javascript方式实现字体颜色计算的公式
var rgb = [255, 0, 0];
function setForegroundColor() {
var sum = Math.round((
(parseInt(rgb[0]) * 299)
+ (parseInt(rgb[1]) * 587)
+ (parseInt(rgb[2]) * 114)) / 1000);
return (sum > 128) ? 'black' : 'white';
原理比较简单,根据RGB值,计算一个值,判断是否大于128,来决定使用black or white
CSS变量如何实现呢?看下面
:root {
--red: 28;
--green: 150;
--blue: 130;
--accessible-color: calc(
(var(--red) * 299) +
(var(--green) * 587) +
(var(--blue) * 114) /
) - 128
) * -1000
.button {
color:
rgb(
var(--accessible-color),
var(--accessible-color),
var(--accessible-color)
background-color:
rgb(
var(--red),
var(--green),
var(--blue)
}
查看效果, 这里
- CSS变量条件语句
使用二元条件的计算
:root {
--is-big: 0;
.is-big {
--is-big: 1;
.block {
padding: calc(
25px * var(--is-big) +
10px * (1 - var(--is-big))
border-width: calc(
3px * var(--is-big) +
1px * (1 - var(--is-big))
}
在这个例子中,我们给所有带有.block的元素都加上了10px的内边距和1px的边框,除非这些元素上的--is-big变量不为1,在这种情况下,它们将分别变为25px和3px. 理解这一点的机制非常简单:我们在使用calc()的单个计算中使用我们的可能值,我们使其中一个值无效,并且基于变量的值得到另一个值,该变量的值可以是1或0。换句话说,我们有25px * 1 + 10px * 0和25px * 0 + 10px * 1两种情况。
更复杂的条件
我们不仅可以使用这个方法在两个可能的值中选择,还可以从3个或者更多的值中选择。然而,对于每增加一个可能的值,计算将变得更加复杂。为了3个可能的值中进行选择,计算将看起来像这样:
.block {
padding: calc(
100px * (1 - var(--foo)) * (2 - var(--foo)) * 0.5 +
20px * var(--foo) * (2 - var(--foo)) +
3px * var(--foo) * (1 - var(--foo)) * -0.5
}
原理是一样的:我们只需要将每个可能的值乘以一个表达式,当该值的条件是我们需要的值时,该值等于
1
,在其他情况下为
0
。这个表达式可以很容易的组成:我们只需要使我们的条件变量的每个其他可能的值无效,这样做后,我们需要在那里添加触发值,看看是否需要调整结果,使其等于1。
- 画一个百分比饼图
.pie {
--p: 25;
width: 300px;
height: 300px;
border-radius: 50%;
background: conic-gradient(#ab3e5b calc(var(--p)*1%), #ef746f 0%);
transition: --p .5s;
position: relative;
.pie:after {
position: absolute;
z-index: 1;
top: 20%;
left: 0;
width: 100%;
text-align: center;
color: #fff;
counter-reset: p var(--p);
content: counter(p) '%';
}
html code
div id="pie" class="pie" style="--p:25"></div>
<button id="increment">add 10</button>
js code
window.addEventListener('DOMContentLoaded', function () {
var p = 25
var pie = document.getElementById('pie')
pie.style.setProperty('--p', p)