Css3 变量(css中为层叠变量而生的自定义属性—模块级别1)

摘要

这个模块介绍了作为一个全新的原生的被所有的CSS属性接受的值类型的层叠变量,且用自定义属性定义他们。CSS是一个描述结构化文档(例如HTML和XML)在屏幕上、纸张上、在语音中等等渲染的语言。

简介

本段仍不是标准的。

大型文档或者应用(以及那些即使是小的)能包含相当多的CSS。在很多的CSS文件中的值会是重复的数据;例如,一个网站可以确立一个颜色体系且整个网站在重用3到4个颜色值。变更这个数据是很困难的且容易出错,因此他在CSS文件(可能是多个CSS文件)中是分散着的,且查找替换并不可靠。

本模块介绍了以自定义属性广知的自定义作者定义属性家族,这允许作者给一个属性通过一个作者自己选择的名字和var()函数来指定任意的值(这允许作者之后在文档的其他地方给其他属性使用这些值)。这使得阅读大型的文件的时候更容易,因为看似无意义的值现在是有了具体含义的名字,且使得修改这样的文件更容易且不易出错,因为在自定义属性中只需要改变这个值一次,且这个改变会自动传播到所有使用这个变量的地方。

定义自定义属性:–*属性家族

本规范定义了一个称为自定义属性(除了其他方面)的开放式的设置属性,用于定义var()函数的替代值。

  • –*

    值: <任意值any-value>

    初始化: (没有,看其他段落)

    应用在: 所有元素

    可继承: 可以

    媒介: 所有媒介

    计算值: 用变量替换指定值(但是参见‘无效变量invalid variables’)

    可动画: 不可以

一个自定义属性是以两个-开始的任意名字,像’–foo’。<自定义属性名custom-property-name>的生产production和这(他是以任何以两个-开始的可用的标识符)是对应的。自定义属性是对作者和用户单独使用的;CSS永远不会给他们一个超出这里所给出的意义。

例子1:

自定义属性定义变量,通过var()记法引用,能应用在多种用途。例如,一个设计上页面自始至终使用一小系列颜色能在自定义变量中存储这些颜色值,在用变量使用它们:

:root {
  --main-color: #06c;
  --accent-color: #006;
}
/* The rest of the CSS file */
#foo h1 {
  color: var(--main-color);
}

命名提供了一个颜色的助记符,避免了在颜色代码中很难认出的错误(错别字),且如果一旦主题颜色变话了,只需在修改一个简单的地方而不是需要在web页面中跨越多个样式表来修改。

不像其他的CSS属性,自定义属性命名是区分大小写的。

例子2:

’–foo’和’–FOO’都是可用的,他们是不同的属性——使用’var(–foo)’将会引用第一个,而使用’var(–FOO)’将会引用第二个。

自定义属性不会被all属性重置。我们可能会在未来定义一个属性来重置所有的变量。

CSS通用关键词也能在自定义属性中使用,和其他属性有同样的意义。

注意:那是他们在层叠值时间解释为正常,且他们不是作为自定义属性的值保存的,因此不替换响应的变量。

注意:由于当前模块关注于自定义属性利用var()函数来使用创建“变量”,他们也能作为真实的自定义属性使用,通过脚本解析和执行。希望能在CSS扩展CSS-EXTENSIONS中会详述这些使用例子且使得他们更容易做。

自定义属性值语法

自定义属性允许的语法是非常宽松的。<任意值any-value>的生产production匹配任何一个或者多个标记tokens序列,只要序列中不包含<坏字符串标记>,<坏的URL标记>,不匹配的<<)-标记token>>,<<]-标记token>>,或<<}-标记token>>,或顶层<分号标记>标记或< delim-token >的值为"!"标记。

注意:这个随着CSS语法规则的定义指出了一个自定义属性值永远不能包含一个不匹配的引号或者括号,所以当再次序列化时像封闭样式规则那样不能对大的语法结构产生影响。

注意:自定义属性能包含’!important’结尾,但是这回自动被CSS解析器从属性值中移除,且使得自定义属性在CSS层叠中”important”。也就是说,在顶层禁止”!”字符并不是阻止’!important’不被使用,由于’!important’在语法检查之前就被移掉了。

注意:由于<任意值any-value>必须代表至少一个标记token,一个标记可能是空格。这意味着'--foo: ;'是有效的,且相应的'var(--foo)'能有一个空格作为他的替代值,但是'--foo:;'是无效的。

例子3:

例如,下边的就是一个合法的自定义属性:

--foo: if(x > 5) this.width = 10;

由于这个值作为变量明显是无用的,因为他在任何的正常属性中都是无效的,他可以通过JavaScript读和执行。

自定义属性值以及var()函数的替代为自定义属性的值是区分大小写的,且必须在他们的原始的作者给定的套管casing中保存。(很多CSS值是不区分大小写的,用户代理能利用canonicalizing将他们放入一个单独的套管中,但是对自定义属性是不允许的。)

自定义属性的初始值是一个空值;也就是啥也没有。这个初始值和var()记法有一个特殊的交互,将在定义var()段落解释。

自定义属性是普通的属性,因此他们能在任何元素上声明,是通过正常的继承和层叠规则解决的,能有@media条件以及其他的条件规则,能在HTML的样式style属性中使用,能使用CSSOM(CSS对象模式CSS Object Model)读和写等等。

特别的是他们还能被过渡transitioned或者动画animated,但是因为用户代理没办法解释他们的内容,他们往往使用用于其他不能智能的以内插值替换的对值的”flips at 50%”行为。然而,在@keyframes规则中使用任何的自定义属性变成了animation-tainted污染动画,这影响了在一个动画属性中通过var()函数引用时如何对待。

例子4:

如下样式:

:root {
  --header-color: #06c;
}

声明了在根元素上的一个名字为’–header-color’的自定义属性,且分配了一个值’#06c’。这个属性然后继承给了再文档中的其余的元素。他的值能用var()函数引用:

h1 { background-color: var(--header-color); }

前边的规则和写’background-color:#06c;’是一样的,除了变量名使得颜色的由来更清晰了,且如果’var(–header-color)’如果在文档中的其他元素上使用,所有的使用都能通过修改在根元素上的’–header-color’属性一次更新。

例子5:

如果一个自定义属性被声明了多次,标准的层叠规则帮助解决他。变量往往从同样的元素上的相关的自定义属性的计算值中得到:

:root { --color: blue; }
div { --color: green; }
#alert { --color: red; }
* { color: var(--color); }

<p>I inherited blue from the root element!</p>
<div>I got green set directly on me!</div>
<div id='alert'>
  While I got red set directly on me!
  <p>I’m red too, because of inheritance!</p>
</div>

解决循环依赖

自定义属性几乎完全是未经估值的,除了他们允许在他们的值中求’var()’函数的值。这能在使用var()引用他自身时或者两个或者更多的自定义属性互相引用彼此时创建循环依赖。

对于每一个元素,创建一个无直接依赖的图形,包含着每一个自定义属性的节点。如果自定义属性prop的值包含了一个引用属性var(包括在’var()’中的备用参数)的var()函数,在prop和var之间增加一个边界(边界有可能从一个自定属性到他自身)。如果在依赖的图中有一个循环,在这个循环中的所有的自定义属性必须计算为他们的初始值。

例子6:

这个例子展示了一个自定义属性安全地使用的变量:

:root {
  --main-color: #c06;
  --accent-background: linear-gradient(to top, var(--main-color), white);
}

当’–main-color’改变时’–accent-background’属性将会自动更新。

例子7:

另一方面,这个例子展示了一个无效的彼此依赖变量的实例:

:root {
  --one: calc(var(--two) + 20px);
  --two: calc(var(--one) - 20px);
}

’–one’和’–two’现在计算为他们的初始值,而不是长度。

注意到在计算值的时间(发生在这个值被继承之前)自定义属性解决他们值中的任何’var()’函数是很重要的。一般情况下,只有当在同一个元素上的多个自定义属性彼此互相引用时才会发生循环依赖;在一个元素的元素树上较高的元素上定义的自定义属性永远不会和在元素树的较低元素上定义的属性发生循环引用。

例子8:

例如,给定的下边的结构,这些自定义属性不是循环的,且都定义了有效的变量:

<one><two><three /></two></one>
one   { --foo: 10px; }
two   { --bar: calc(var(--foo) + 10px); }
three { --foo: calc(var(--bar) + 10px); }

< one >元素为’–foo’定义了一个值。< two >元素继承了这个值,且额外的用’foo’变量给’–bar’分配了一个值。最后,< three >元素在变量替代之后继承了’–bar’值,且然后依据那个值重新定义了’–foo’。由于那个值是继承的,’–bar’不在包含在< one >上定义的’–foo’属性的引用,所以使用’var(–bar)’变量定义’–foo’不是循环的,且实际上定义的值将最终算为’30px’。

使用层叠变量:’var()’记法

自定义属性的值能用’var()’函数取代到另一个属性的值中。’var()’的语法是:

var() = var( <自定义属性名custom-property-name> [, <任何值any-value> ]? )

‘var()’函数能取代在一个元素上的任何属性值的任何部分来使用。’var()’函数不能作为属性名,选择符或者除了属性值的其他任何东西来使用。(这样做通常会产生无效语法,或者其他一个值的意义和变量没有任何联系。)

函数的第一个参数是要被替换的自定义属性的名字。如果提供第二个参数了的话,他是一个备用值,当引用的自定义属性无效时作为替换值使用。

注意:和其他自定义属性很像,备用的语法允许逗号。例如,’var(–foo, red, blue)’定义了一个’red, blue’的备用;也就是第一个逗号和这个函数结尾之间的任何东西都被视为备用值。

如果一个属性包含了一个或者更多的’var()’函数,且那些函数在语法上是有效的,那么整个属性的语法必须在解析时间就假定为有效。在’var()’函数已经被替换之后,计算值的时间只有语法检查。

为了替换一个属性值中的一个var():

  1. 如果’var()’函数的第一个参数的自定义属性名是animation-tainted污染动画的,且’var()’函数将要用在animation属性或者他的其中一个普通写法中,那么就将这个自定义属性以在这个算法中的其余部分有他的初始值来对待。

  2. 如果’var()’函数的第一个参数的自定义属性名的值是除了初始值意外的任何东西,那么就将’var()’函数替换为对应的自定义属性的值。

  3. 其他情况,如果’var()’函数的第二个参数有备用值,那么就将’var()’函数替换为备用值。如果在备用中有任何的’var()’引用的话,也替换他们。

  4. 其他情况,包含’var()’函数的属性在计算值时间是无效的。

注意:其他的东西也能使得一个属性在计算值时间无效。

例子9:

备用值允许某些类型的防御性编码。例如,作者可以打算创建一个可以在一个大型应用中引入的组件,且使用变量来设置样式以便大型应用的作者能很容易的为了和这个应用其余部分匹配而给这个组件设置主题。

如果没有备用,应用的作者必须为你的组件中使用的每一个变量提供一个值。有备用的话,组件作者能提供默认值,因此应用作者只需要提供他们想覆盖的变量的值。

/* In the component’s style: */
.component .header {
  color: var(--header-color, blue);
}
.component .text {
  color: var(--text-color, black);
}

/* In the larger application’s style: */
.component {
  --text-color: #080;
  /* header-color isn’t set,
     and so remains blue,
     the fallback value */
}

例子10:

例如,下边的代码错误尝试使用一个变量作为属性名:

.foo {
  --side: margin-top;
  var(--side): 20px;
}

这个设置’margin-top: 20px;’是不相等的。反而,第二条声明因为其有无效属性名所以就直接作为语法错误丢弃。

类似的,你不能创建一个由一个变量的一部分的单独的标记token:

.foo {
  --gap: 20;
  margin-top: var(--gap)px;
}

再次提醒,这个设置’margin-top: 20px;’(一耳光长度)是不相等的。反而,他是和’margin-top: 20 px;’(一个数字后边跟着一个标识符)相等的,这个值对于margin-top属性是无效的。注意,可是calc()能用在有效实现同样的事情上,像这样:

.foo {
  --gap: 20;
  margin-top: calc(var(--gap) * 1px);
}

‘var()’函数是在计算值时间被替换的。如果一个声明(一旦所有的’var()’函数被替换掉了)是无效的,这个声明在计算值时间就是无效的。

例子11:

例如,从语法角度来看下边的用法是好的,但是变量替换的结果却是毫无意义的:

:root { --looks-valid: 20px; }
p { background-color: var(--looks-valid); }

由于’20px’对于background-color是无效的值,作为替代,这个属性的实例计算为transparent(background-color的初始值)。

如果这个属性是从默认继承来的,例如color,那么他会计算为继承而来的值而不是初始值。

无效变量

当一个自定义变量有他的初始值,’var()’函数不能替换使用。试图这样做的话会使得声明在计算值时间无效,除非指定了一个有效的备用。

如果一个声明包含了一个引用一个自定义属性(值是其初始值)的’var()’,那么就像上边解释的那样,他在计算值时间是无效的;或者他使用一个有效的自定义属性,但是这个属性值(在替换了他的’var()’函数之后)是无效的。当这种情况发生时,这个属性的计算值可以是这个属性的继承而来的值也可以是他的初始值,这取决于这个属性是否能继承,就好像这个属性值被指定为’unset’关键词。

例子12:

例如,下边的代码:

:root { --not-a-color: 20px; }
p { background-color: red; }
p { background-color: var(--not-a-color); }

< p >元素将会有一个透明背景,而不是红色背景。如果自定义属性自身未设置或者包含看一个无效的’var()’函数的话,同样的事情也会发生。

注意这和如果作者在样式表中直接写’background-color: 20px;’(那会是一个正常语法错误,将导致这个规则被丢弃,因此’background-color: red;’这个规则就会作为替代使用)之间的区别。

注意:‘在计算值时间无效’的概念存在,因此变量不能像其他语法错误那样能“及早失败fail early”,所以用户代理能意识到这个属性的值是无效的时候,他已经丢弃了其他的层级值。

简写属性中的变量

在简写属性中使用’var()’函数存在一些独特的困难。

一般情况下,一个简写属性的值在解析时间就被分离为他的构成的普通写法的属性,且然后普通写法他们自身参与到层叠中,同时简写或多或少的被丢弃了。然而,如果一个’var()’函数在简写中使用,他不能告知值在哪里;实际上在解析时间久分离他们是不可能的,因为一个单独的’var()’函数可能一次替换多个简写的值。

为了避开这一点,实现者必须用一个特殊的,作者察觉不到的‘即将替换的值pending-substitution value’(表明简写包含一个变量,因此普通写法的值就是用未定的变量来替换)来填充普通写法。这个值然后必须像正常情况那样层叠,且在计算值时间(在’var()’函数最终替换进来之后)简写必须被解析且同时必须给定普通写法他们适当的值。

如果API允许注意他们的话,即将替换的值必须序列化为空字符串。

类似的,由于CSSOM定义了简写属性通过他们相应的普通写法的对应的值来序列化,简写包含了’var()’函数的话就必须保留他们原始的(包含着var())值。如果序列化一个简写的值会涉及序列化一个即将替换的值的话,那么作为替换,这个简写就必须通过序列化他的原始值来序列化。

APIs

所有的自定义属性声明都有设置区分大小写的标志。

注意:自定义属性不会在一个驼峰式的CSSStyleDeclaration对象上出现,因为他们的名字可能都有大写和小写的字母(他们标明了不同的自定义属性)。自动完成驼峰式文本变换的方式与此不符。

序列化自定义属性

自定义属性名必须用作者提供的大小写来序列化。

一般情况下,属性名受限于ASCII的范围并且是不区分大小写的,因此实现者通常将名字小写化来序列化。

更新于2014-09-15

上边的只是在CSS中使用变量,那么怎么在JS中访问自定义属性呢?有一篇文章css-variables-updating-custom-properties-javascript介绍了,主要是通过getPropertyValuesetProperty来取值和修改值的。

发布于: 2014年 08月 20日