很多流行的CSS预处理/后处理程序已经实现了样式变量概念,实际上大部分浏览器也实现了原生css变量功能,本篇日志讲解css变量使用说明。文章大部份内容分来自:© w3cplus.com

以下几点是未来CSS属性的简短说明:

  • 动态性,可以在运行时更改
  • 可以方便的从JS中读/写
  • 可继承,可组合,同时具有作用域

CSS中的变量给了我们诸多优点:方便、代码重用、更可靠的代码库和提升防错能力。CSS变量用起来很方便,但是浏览器对其支持情况不太好

概要

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
:root {
--base-font-size: 16px;
--link-color: #6495ed;
}

p {
font-size: var( --base-font-size );
}

a {
font-size: var( --base-font-size );
color: var( --link-color );
}

基础

当使用CSS变量时,你应该了解的三个主要组成:

  • 自定义属性
  • var()函数
  • :root伪类

自定义属性

你已经知道了一些CSS的标准属性,如color,margin,width和font-size。
下面的示例使用了CSS的color属性:

1
2
3
body {
color: #000000; /* The "color" CSS property */
}

自定义属性仅意味着我们(创建CSS文件的人)去定义属性的名字。
为了自定义一个属性名,我们需要用–作为前缀。
如果我们要创建一个值为黑色的、名为text-color的自定义属性,可以这样做:

1
2
3
:root {
--text-color: #000000; /* A custom property named "text-color" */
}

var()函数
为了应用自定义属性,需要利用var()函数,否则浏览器不知道他们代表什么。
如果想要在p、h1和h2中的样式中使用–text-color,可以这样使用var()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
:root {
--text-color: #000000;
}

p {
color: var( --text-color );
font-size: 16px;
}

h1 {
color: var( --text-color );
font-size: 42px;
}

h2 {
color: var( --text-color );
font-size: 36px;
}

其等价于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
p {
color: #000000;
font-size: 16px;
}

h1 {
color: #000000;
font-size: 42px;
}

h2 {
color: #000000;
font-size: 36px;
}

自定义属性是大小写敏感的(和普通的CSS规则不一样)

:root伪类

我们需要一个地方来放置自定义属性。虽然可以在任何样式规则中指定自定义属性,但到处定义属性并不是一个好主意,这对CSS的可维护性和可阅读性是一个挑战。
:root 伪类代表了HTML文档的根元素,这是一个放置自定义属性的好位置,因为我们可以通过其他更具特异性的选择器来覆盖自定义属性值。

第一个CSS变量

令人感到惊讶的是,你们可能已经了解或使用过一个CSS变量(可以看做是第一个)—— currentColor,它并不出名,但仍然可用,并且在所有浏览器工作。

它也有一个作用域,并且能够被重新定义:

1
2
3
4
5
6
7
:root {
color: red;
}
div {
/* border-color is red */
border: 1px solid currentColor;
}

如果你加上下面代码:

1
2
3
div {
color: black;
}

边框将变成黑色。

CSS变量语法

定义

用这样的方式来声明一个变量:–variable-name: variable-value;(变量名是大小写敏感的)。

变量的值可以是颜色、字符串、多个值的组合等:

1
2
3
4
5
6
7
8
9
10
11
: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);
}

可能语法看起来有些丑陋,却是有原因的。比如不能使用像$var这样的变量语法,因为已经被其他的CSS预处理程序使用了。

用法

以这样的方式来使用一个变量: some-css-value: var(–variable-name [, declaration-value]);

1
2
3
p {
margin: var(--p-margin, 0 0 10px);
}

在上面的例子中,如果–p-margin没有被指定,则使用值:0 0 10px。这样的设计在使用时非常灵活–你可以使用一些来自某个框架的变量(通常大部分变量定义在这里),同时当你想要移除它的时候,可以保持现有功能正常工作。

作用域

正如文档中提到的:自定义属性遵循CSS级联规则

使用:root 作用域来定义全局变量:

1
2
3
:root{
--global-var: 'global';
}

如果想让某个变量只在部分元素/组件下可见,只需要在特定的元素下定义该变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="block">
My block is
<div class="block__highlight">awesome</div>
</div>

.block {
--block-font-size: 1rem;
font-size: var(--block-font-size);
}
.block__highlight {
--block-highlight-font-size: 1.5rem;
font-size: var(--block-highlight-font-size);
}

媒体查询也可以提供作用域:

1
2
3
4
5
@media screen and (min-width: 1025px) {
:root {
--screen-category: 'desktop';
}
}

下面一个例子来展示伪类下的作用域(例如,:hover):

1
2
3
4
5
6
7
8
9
body {
--bg: #f00;
background-color: var(--bg);
transition: background-color 1s;
}

body:hover {
--bg: #ff0;
}

由于自定义属性是全局的,为了避免冲突,最好按照统一的约定来命名变量(或者简单的遵循BEM命名法来形成”命名空间“),例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
:root {
/* main (page-wide) variables */
--main-color: #555;
--main-bg: rgb(200, 200, 200);
/* accordion variables */
--accordion-bg: #f00;
--accordion-font-size: 1.5rem;
--accordion__item-bg: #ded;
}

body {
color: var(--main-color);
background-color: var(--main-bg);
/*...*/
}

变量组合

变量可以和其他变量组合使用,–variable-name: var(–another-variable-name);:

1
2
3
4
5
6
7
8
9
10
11
12
.block {
--block-text: 'This is my block';
--block-highlight-text: var(--block-text)' with highlight';
}

.block:before {
content: var(--block-text);
}

.block__highlight:before {
content: var(--block-highlight-text); /*This is my block with highlight*/
}

这里有个问题 – 声明新变量的值不能直接由一个已定义的变量计算而来,但我们可以使用CSS calc()来代替:

1
2
3
4
5
6
7
8
9
10
11
12
.block {
--block-font-size: 1rem;
}

.block__highlight {
/* DOESN'T WORK */
--block-highlight-font-size: var(--block-font-size)*1.5;
font-size: var(--block-highlight-font-size);

/* WORKS */
font-size: calc(var(--block-font-size)*1.5);
}

对复杂的表达式要格外留心,它们很可能会影响到应用的性能。

值的计算(calc())

正如上文提到的,不能简单的这样使用变量:

1
padding: var(--spacer)px

但借助calc()即可以实现上面功能以及其他的计算。看一个简单的例子:

1
margin: 0 0 calc(var(--base-line-height, 0) * 1rem);

最后,可以随时重置/继承变量的值

CSS自定义属性默认是继承的,在这个例子中,通过重置自定义属性,可以消除模块/组件受到的影响:

1
2
3
4
5
.with-reset {
--bgcolor: initial;/* RESETS VALUE */
--color: green;/* CHANGES VALUE */
--border: inherit;/* DOESN'T CHANGE ANYTHING, AS INHERITED BY DEFAULT */
}

在JS中使用原生属性

使用CSS样式声明接口,可以在JS中方便的读/写自定义属性(getPropertyValue, setProperty):

1
2
3
4
5
6
7
// READ
const rootStyles = getComputedStyle(document.documentElement);
const varValue = rootStyles.getPropertyValue('--screen-category').trim();

// WRITE
document.documentElement.style.setProperty('--screen-category', value);
下面是使用自定义属性--screen-category的例子----screen-category变量描述了当前屏幕类型,同时在UI上可以被组合使用。

例子中展示了一种简单的调试自定义属性的方法。JS代码:

1
2
3
4
5
6
7
8
9
10
11
12
// GET
alert(
getComputedStyle(document.documentElement).getPropertyValue('--screen-category').trim();
);

// SET
document.documentElement.style.setProperty('--screen-category', 'custom');

// or reassign from an another prop
document.documentElement.style.setProperty(
'--screen-category', 'var(--default-screen-category, '%DEFAULT VALUE IF VAR IS NOT SET%')'
);

CSS变量值的组合能力以及JS提供的方便的读写接口,可以让我们告别老的从CSS/Sass中传数据给JS的hack方式(例如:媒体查询断点列表)。

如果是为了调试, 可以通过content在页面上输出变量的值:

1
2
3
body:after {
content: '--screen-category : 'var(--screen-category);
}

浏览器支持

CSS自定义属性已经在正式版Chrome,Firefox和桌面版Safari 9.1中获得支持:

同时也在微软Edge浏览器的考虑支持中

目前有一些限制和错误:

  • 在一些浏览器中,针对CSS变量的复杂calc()运算可能不能工作。
  • 在当前作用域下的所有自定义属性上应有公共规则(如,reset)的功能在讨论中。就像这样:–: initial;
  • 不能使用CSS自定义属性作为CSS属性名称:var(–side): 10px;
  • 进行calc()运算时,最好能提供默认值: calc(var(–base-line-height, 0) * 1rem)
  • 不能作为媒体查询值使用:@media screen and (min-width: var(–desktop-breakpoint) {
  • 图片地址,如url(var(–image-url)) ,不会生效

这是检测浏览器是否支持CSS自定义属性的方法。CSS:

1
2
3
4
5
6
7
@supports ( (--a: 0)) {
/* supported */
}

@supports ( not (--a: 0)) {
/* not supported */
}

JS:

1
2
3
4
5
if (window.CSS && window.CSS.supports && window.CSS.supports('--a', 0)) {
alert('CSS properties are supported');
} else {
alert('CSS properties are NOT supported');
}

对于老的浏览器(没有CSS.supports()方法),可以使用Wes Bos的测试方法。

Fallbacks / polyfills
有很多PostCSS插件的实例,但他们的实现没有完全正确而又和标准一致的,更重要的是,他们都不是动态的。

CSS Houdini团队的针对所有主流浏览器的一种简单的原生CSS “polyfills”方法即将到来,这无疑为未来带来曙光。但即便如此绝大多数的变量语法依然不能很好的支持。

但目前为止,下面这些依然是值得关注的:

  • PostCSS plugin to transform W3C CSS Custom Properties – 简单的插件,只能处理:root作用域下的变量声明
  • PostCSS插件,用来将CSS自定义属性(CSS变量)转换为静态的表达式 ,还有它的线上实例.
  • Myth- 一个预处理器
  • cssnext可以让许多CSS的新语法立即能够正常工作。
  • 和CSS预处理器(SCSS)一起使用

相同变量名

有一个小建议,使用浏览器支持检测,来开始CSS自定义属性和预处理器的协同使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@supports ( (--a: 0)) {
/* Custom properties are supported in the browser */
:root{
--main-bg: #4d4e53;
}

body {
background-color: var(--main-bg);
}
}

@supports ( not (--a: 0)) {
/* Custom properties are NOT supported in the browser */
$main-bg: #4d4e53;

body {
background-color: $main-bg;
}
}

在这个实例中,CSS变量和Sass变量都被创建了,但只有在浏览器不支持CSS自定义属性时,Sass变量才会生效。

你也可以去除这段逻辑,利用Sass的mixin来隐藏它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@mixin setVar($varName, $value){
@include setVarSass($varName, $value);
@include setVarCss($varName, $value);
}

@mixin setPropFromVar($propName, $varName){
@supports ( (--a: 0)) {
// Custom properties are supported in the browser
#{$propName}: getVarCss($varName);
}

@supports ( not (--a: 0)) {
// Custom properties are NOT supported in the browser
#{$propName}: getVarSass($varName);
}
}

// SET
@include setVar('main-color', #f00);

// GET
body {
@include setPropFromVar('color', 'main-color');
}

全局变量

变量作用域的相关理念在Sass和CSS中是不同的,这里是一个通用的实现方法:

1
2
3
4
5
6
7
/* SCSS */
$main-color: #f00 !global;

/* CSS */
:root{
--main-color: #f00;
}

只关联没有被关联使用的变量

一个常见的情况是,当你期望可能已经定义的变量,在未被分配时才被应用到另一个值:

1
2
3
4
5
6
7
8
9
10
11
12
/* SCSS */
$main-color: #f00 !default;

body{
color: $main-color;
}
不幸的是,在CSS中不能简单的就使用它:

/* CSS */
body{
--main-color: var(--main-color, #f00); /* DOESN'T WORK */
}

但是你可以创建一个新变量

1
2
3
4
5
6
7
8
9
10
11
/* CSS */
body{
--local-main-color: var(--main-color, #f00); /* DOES WORK */
color: var(--local-main-color);
}
或者在使用的时候这样做:

/* CSS */
body{
color: var(--main-color, #f00); /* DOES WORK */
}

总结

现在你已经知道了什么是CSS自定义属性,还有:

  • CSS和JS的语法级互动
  • 动态性、可继承、可组合,同时拥有作用域
  • 浏览器支持以及如何可靠地使用可以和Sass变量一起使用
  • 自定义变量拓展了开发者和web平台的能力,随之产生了一些有趣的用法和案例

Chrome 使用CSS变量calc()不支持乘/除以非整数
Firefox不支持rgba()内的calc()自定义属性
Safari支持Demo演示 :blush:

###扩展阅读
CSS的原生mixins语法已经公布 – 更多文章:CSS @apply rule (native CSS mixins)