1. 简介
本节不具有规范性。
大型文档或应用程序(甚至小型项目)可能包含相当多的 CSS。CSS 文件中的许多值通常是重复数据;例如,一个网站可能建立了一种配色方案,并在全站范围内重复使用三到四种颜色。修改这些数据可能既困难又容易出错,因为它们分散在 CSS 文件中(甚至可能分布在多个文件中),且可能无法通过“查找与替换”轻松处理。
本模块引入了一系列由作者定义的自定义属性,统称为自定义属性,允许作者为自定义名称的属性分配任意值;同时引入了 var() 函数,允许作者在文档其他地方的属性中使用这些值。这使得阅读大型文件变得更加容易,因为看似随意的值现在有了信息丰富的名称;同时也使得编辑此类文件变得更加简单且不易出错,因为只需在自定义属性中修改一次值,更改就会自动传播到该变量的所有使用处。
1.1. 值定义
本规范遵循 CSS 属性定义约定([CSS2]),使用 值定义语法([CSS-VALUES-3])。本规范未定义的值类型在 CSS Values & Units [CSS-VALUES-3] 中定义。与其他 CSS 模块的组合可能会扩展这些值类型的定义。
除了定义中列出的属性特定值外,本规范中定义的所有属性也都接受 CSS 全局关键字 作为其属性值。为了可读性,未明确重复列出。
2. 定义自定义属性:--* 属性家族
本规范定义了一组开放式的属性,称为自定义属性,除其他用途外,它们还用于定义 var() 函数的替代值。
| 名称 | --* |
|---|---|
| 值 | <declaration-value>? |
| 初始值 | 保证无效值 |
| 应用于 | 所有元素和所有伪元素(包括具有限制属性列表的元素) |
| 可继承 | 是 |
| 百分比 | 不可用 |
| 计算值 | 代入变量后的指定值,或保证无效值 |
| 规范顺序 | 按语法 |
| 动画类型 | 离散 (discrete) |
用户代理应在所有介质(包括非可视介质)上支持此属性。
任何名称以两个连字符(U+002D HYPHEN-MINUS)开头的属性都是自定义属性,例如 --foo。<custom-property-name> 产生式对应于此:它定义为任何 <dashed-ident>(以两个连字符开头的有效标识符),但 -- 本身除外,它是保留给 CSS 未来使用的。自定义属性仅供作者和用户使用;CSS 永远不会赋予它们超出本文所述的含义。
测试
- variable-declaration-29.html (实时测试) (源码)
- variable-declaration-31.html (实时测试) (源码)
- variable-declaration-32.html (实时测试) (源码)
- variable-declaration-33.html (实时测试) (源码)
- variable-declaration-34.html (实时测试) (源码)
- variable-declaration-35.html (实时测试) (源码)
- variable-declaration-36.html (实时测试) (源码)
- variable-declaration-40.html (实时测试) (源码)
- variable-declaration-41.html (实时测试) (源码)
- variable-declaration-42.html (实时测试) (源码)
- variable-empty-name-reserved.html (实时测试) (源码)
:root{ --main-color : #06c; --accent-color : #006; } /* The rest of the CSS file */ #foo h1{ color : var ( --main-color); }
这种命名为颜色提供了助记符,防止了颜色代码中难以察觉的拼写错误,并且如果主题颜色发生变化,只需将更改聚焦在一个简单的地方(自定义属性值),而无需对网页中的所有样式表进行多次编辑。
与其他 CSS 属性不同,自定义属性名称不是 ASCII 大小写不敏感的。相反,自定义属性名称仅在彼此完全相同时才相等。
也许更令人惊讶的是,--foó 和 --foó 是不同的属性。前者由 U+00F3(拉丁小写字母 O 带锐音符)拼写,而后者由 ASCII “o” 后跟 U+0301(组合锐音符)拼写,且“完全相同”关系使用直接的码点对码点比较来确定两个字符串是否相等,以避免 Unicode 规范化和区域特定排序带来的复杂性和陷阱。
操作系统、键盘或输入法有时会使用不同的码点序列对视觉上相同的文本进行编码。建议作者选择能避免潜在混淆的变量名称,或使用转义等手段来确保外观相似的序列是相同的。示例见 [CHARMOD-NORM] 第 2.3 节。
--fijord : red; --fijord : green; --fijord : blue; .test{ background-color : var ( --fijord); }
原因是第一个自定义属性使用了字符序列:拉丁小写字母 F + 拉丁小写字母 I + 拉丁小写字母 J;第二个外观相同的属性使用了字符序列:拉丁小写字母 F + 拉丁小写连字 IJ,而第三个使用了字符序列:拉丁小写连字 FI + 拉丁小写字母 J。
因此该 CSS 包含三个不同的自定义属性,其中两个是未使用的。
自定义属性不会被 all 属性重置。我们将来可能会定义一个重置所有变量的属性。
CSS 宽关键词可在自定义属性中使用,其含义与在任何其他属性中相同。
测试
- variable-declaration-43.html (实时测试) (源码)
- variable-declaration-44.html (实时测试) (源码)
- variable-declaration-45.html (实时测试) (源码)
- variable-declaration-46.html (实时测试) (源码)
- variable-declaration-47.html (实时测试) (源码)
- variable-declaration-56.html (实时测试) (源码)
- variable-declaration-57.html (实时测试) (源码)
- variable-declaration-58.html (实时测试) (源码)
- variable-declaration-60.html (实时测试) (源码)
- variable-definition-keywords.html (实时测试) (源码)
注意:也就是说,它们在层叠值时正常解释,并没有作为自定义属性的值被保留,因此不会被相应的变量替换。
注意:虽然本模块重点在于使用自定义属性和 var() 函数来创建“变量”,但它们也可以作为实际的自定义属性使用,由脚本解析并操作。预计 CSS 扩展规范 [CSS-EXTENSIONS] 将扩展这些用例并使其更易于实现。
自定义属性是普通属性,因此它们可以声明在任何元素上,通过正常的继承和层叠规则解析,可以使用 @media 和其他条件规则进行条件化,可以在 HTML 的 style 属性中使用,可以通过 CSSOM 读取或设置等。
测试
- css-vars-custom-property-inheritance.html (实时测试) (源码)
- variable-created-document.html (实时测试) (源码)
- variable-created-element.html (实时测试) (源码)
- variable-cssText.html (实时测试) (源码)
- variable-declaration-06.html (实时测试) (源码)
- variable-definition-cascading.html (实时测试) (源码)
- variable-external-declaration-01.html (实时测试) (源码)
- variable-external-reference-01.html (实时测试) (源码)
- variable-external-supports-01.html (实时测试) (源码)
- variable-first-letter.html (实时测试) (源码)
- variable-first-line.html (实时测试) (源码)
- variable-pseudo-element.html (实时测试) (源码)
- variable-reference-13.html (实时测试) (源码)
- variable-reference-14.html (实时测试) (源码)
- variable-reference-shorthands.html (实时测试) (源码)
- variable-reference-visited.html (实时测试) (源码)
值得注意的是,它们甚至可以过渡或动画化,但由于 UA 无法解释其内容,它们始终使用任何其他无法智能插值的成对值所采用的“在 50% 处翻转”的行为。然而,任何在 @keyframes 规则中使用的自定义属性都会变得受动画影响,这会影响在动画属性中通过 var() 函数引用它时的处理方式。
测试
- variable-animation-from-to.html (实时测试) (源码)
- variable-animation-over-transition.html (实时测试) (源码)
- variable-animation-substitute-into-keyframe-shorthand.html (实时测试) (源码)
- variable-animation-substitute-into-keyframe-transform.html (实时测试) (源码)
- variable-animation-substitute-into-keyframe.html (实时测试) (源码)
- variable-animation-substitute-within-keyframe-fallback.html (实时测试) (源码)
- variable-animation-substitute-within-keyframe-multiple.html (实时测试) (源码)
- variable-animation-substitute-within-keyframe.html (实时测试) (源码)
- variable-animation-to-only.html (实时测试) (源码)
受动画影响是“传染性的”:引用了受动画影响属性的自定义属性也会变得受动画影响。
:root{ --header-color : #06c; }
在根元素上声明了一个名为 --header-color 的自定义属性,并为其分配了值“#06c”。该属性随后被继承到文档其余部分的元素中。其值可以通过 var() 函数引用。
h1{ background-color : var ( --header-color); }
前述规则等同于编写 background-color: #06c;,只是变量名使颜色的来源更清晰,并且如果文档中的其他元素使用了 var(--header-color),则可以通过更改根元素上的 --header-color 属性来一次性更新所有使用处。
: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>
:root, :root:lang ( en) { --external-link : "external link" ;} :root:lang ( el) { --external-link : "εξωτερικός σύνδεσμος" ;} a[ href^="http" ] ::after{ content : " (" var ( --external-link) ")" }
变量声明甚至可以保留在单独的文件中,以使翻译的维护更简单。
2.1. 自定义属性值语法
允许的自定义属性语法极其宽松。<declaration-value> 产生式匹配任何一个或多个令牌序列,只要该序列不包含 <bad-string-token>、<bad-url-token>、不匹配的 <)-token>、<]-token> 或 <}-token>,或者顶层的 <semicolon-token> 令牌或值为“!”的 <delim-token> 令牌。
测试
此外,如果自定义属性的值包含 var() 引用,该 var() 引用必须根据指定的 var() 语法有效。否则,该自定义属性无效,必须忽略。
注意:此定义连同通用 CSS 语法规则,暗示自定义属性值从不包含不匹配的引号或括号,因此在重新序列化时,对较大的语法结构(如封闭的样式规则)没有任何影响。
注意:自定义属性可以包含尾随的 !important,但这会被 CSS 解析器自动从属性值中移除,并使该自定义属性在 CSS 层叠中变得“重要”。换句话说,对顶层“!”字符的禁止并不妨碍 !important 的使用,因为 !important 在语法检查发生前已被移除。
--foo : if ( x >5 ) this.width =10 ;
虽然这个值作为变量显然没用(因为它在任何正常属性中都是无效的),但它可能被 JavaScript 读取和操作。
自定义属性的值,以及代入自定义属性中的 var() 函数的值,是大小写敏感的,且必须保留其作者给定的原始大小写。(许多 CSS 值是 ASCII 大小写不敏感的,用户代理可以通过将它们“规范化”为单一大小写来利用这一点,但自定义属性不允许这样做。)
这带来了一些连锁影响。例如,CSS 中的相对 URL 是针对该值所在样式表的基 URL 进行解析的。然而,如果类似 --my-image: url(foo.jpg); 的自定义属性出现在 样式表中,它不会立即解析为绝对 URL;如果该变量稍后在另一个 样式表中被用作 background: var(--my-image);,它将在那个点解析为 。
2.2. 保证无效值
自定义属性的初始值是一个保证无效值。正如 § 3 使用层叠变量:var() 标记法中所定义,使用 var() 来代入以其为值的自定义属性,会使引用它的属性变得在计算值时无效。
此值序列化为空字符串,但实际上将空值写入自定义属性(如 --foo: ;)是一个有效的(空)值,而非保证无效值。如果由于某种原因想要手动将变量重置为保证无效值,可以使用关键字 initial 来实现。
2.3. 解决依赖循环
自定义属性几乎完全保持未求值状态,除了它们允许并求值其值中的 var() 函数。这可能会产生循环依赖,其中自定义属性使用引用自身的 var(),或者两个或多个自定义属性各自尝试引用对方。
对于每个元素,创建一个有向依赖图,包含每个自定义属性的节点。如果自定义属性 prop 的值包含一个引用属性 var 的 var() 函数(包括在 var() 的回退参数中),则在 prop 和 var 之间添加一条边。边可能从自定义属性指向其自身。
如果依赖图中存在循环,则循环中的所有自定义属性都是在计算值时无效的。
测试
注意:参与依赖循环的已定义属性,要么其值中最终包含无效变量(从而变得在计算值时无效),要么定义了它们自己的循环处理方式(如 font-size 使用 em 值)。它们不会像自定义属性那样计算为保证无效值。
:root{ --main-color : #c06; --accent-background : linear-gradient ( to top, var ( --main-color), white); }
--accent-background 属性(以及任何其他使用 var(--main-color) 的属性)将在 --main-color 属性更改时自动更新。
需要特别注意的是,自定义属性在计算值时解析其值中的任何 var() 函数,这发生在继承值之前。通常,循环依赖仅在同一元素上的多个自定义属性相互引用时发生;在元素树中较高位置的元素上定义的自定义属性永远不会导致与元素树中较低位置的元素上定义的属性产生循环引用。
< one >< two >< three /></ two ></ one > < style > one { --foo : 10 px ; } two { --bar : calc( var ( --foo ) + 10 px ); } three { --foo : calc( var ( --bar ) + 10 px ); } </ style >
<one> 元素为 --foo 定义了一个值。<two> 元素继承此值,并额外使用 foo 变量为 --bar 分配了一个值。最后,<three> 元素在变量替换之后继承 --bar 值(换句话说,它看到的值是 calc(10px + 10px)),然后根据该值重新定义 --foo。由于它继承的 --bar 值不再包含对 <one> 上定义的 --foo 属性的引用,因此使用 var(--bar) 变量定义 --foo 并非循环,实际上定义了一个最终(在普通属性中作为变量引用时)解析为 30px 的值。
3. 使用层叠变量:var() 标记法
自定义属性的值可以通过 var() 函数代入另一个属性的值中。var() 的语法为
var () =var ( <custom-property-name>, <declaration-value>?)
测试
- variable-reference-07.html (实时测试) (源码)
- variable-reference-08.html (实时测试) (源码)
- variable-reference-09.html (实时测试) (源码)
- variable-reference-10.html (实时测试) (源码)
- variable-reference-17.html (实时测试) (源码)
- variable-reference-20.html (实时测试) (源码)
- variable-reference-21.html (实时测试) (源码)
- variable-reference-22.html (实时测试) (源码)
- variable-reference-23.html (实时测试) (源码)
- variable-reference-24.html (实时测试) (源码)
- variable-reference-25.html (实时测试) (源码)
- variable-reference-28.html (实时测试) (源码)
- variable-reference-29.html (实时测试) (源码)
- variable-reference-31.html (实时测试) (源码)
- variable-reference-32.html (实时测试) (源码)
- variable-reference-33.html (实时测试) (源码)
- variable-reference-34.html (实时测试) (源码)
- variable-reference-35.html (实时测试) (源码)
- variable-reference.html (实时测试) (源码)
@supports
- variable-supports-01.html (实时测试) (源码)
- variable-supports-02.html (实时测试) (源码)
- variable-supports-03.html (实时测试) (源码)
- variable-supports-04.html (实时测试) (源码)
- variable-supports-05.html (实时测试) (源码)
- variable-supports-06.html (实时测试) (源码)
- variable-supports-07.html (实时测试) (源码)
- variable-supports-08.html (实时测试) (源码)
- variable-supports-09.html (实时测试) (源码)
- variable-supports-10.html (实时测试) (源码)
- variable-supports-11.html (实时测试) (源码)
- variable-supports-12.html (实时测试) (源码)
- variable-supports-13.html (实时测试) (源码)
- variable-supports-14.html (实时测试) (源码)
- variable-supports-15.html (实时测试) (源码)
- variable-supports-16.html (实时测试) (源码)
- variable-supports-17.html (实时测试) (源码)
- variable-supports-18.html (实时测试) (源码)
- variable-supports-19.html (实时测试) (源码)
- variable-supports-20.html (实时测试) (源码)
- variable-supports-21.html (实时测试) (源码)
- variable-supports-22.html (实时测试) (源码)
- variable-supports-23.html (实时测试) (源码)
- variable-supports-24.html (实时测试) (源码)
- variable-supports-25.html (实时测试) (源码)
- variable-supports-26.html (实时测试) (源码)
- variable-supports-27.html (实时测试) (源码)
- variable-supports-28.html (实时测试) (源码)
- variable-supports-29.html (实时测试) (源码)
- variable-supports-30.html (实时测试) (源码)
- variable-supports-31.html (实时测试) (源码)
- variable-supports-32.html (实时测试) (源码)
- variable-supports-33.html (实时测试) (源码)
- variable-supports-34.html (实时测试) (源码)
- variable-supports-35.html (实时测试) (源代码)
- variable-supports-36.html (实时测试) (源代码)
- variable-supports-37.html (实时测试) (源代码)
- variable-supports-38.html (实时测试) (源代码)
- variable-supports-39.html (实时测试) (源代码)
- variable-supports-40.html (实时测试) (源代码)
- variable-supports-41.html (实时测试) (源代码)
- variable-supports-42.html (实时测试) (源代码)
- variable-supports-43.html (实时测试) (源代码)
- variable-supports-44.html (实时测试) (源代码)
- variable-supports-45.html (实时测试) (源代码)
- variable-supports-46.html (实时测试) (源代码)
- variable-supports-47.html (实时测试) (源代码)
- variable-supports-48.html (实时测试) (源代码)
- variable-supports-49.html (实时测试) (源代码)
- variable-supports-50.html (实时测试) (源代码)
- variable-supports-51.html (实时测试) (源代码)
- variable-supports-52.html (实时测试) (源代码)
- variable-supports-53.html (实时测试) (源代码)
- variable-supports-54.html (实时测试) (源代码)
- variable-supports-55.html (实时测试) (源代码)
- variable-supports-56.html (实时测试) (源代码)
- variable-supports-57.html (实时测试) (源代码)
- variable-supports-58.html (实时测试) (源代码)
- variable-supports-59.html (实时测试) (源代码)
- variable-supports-60.html (实时测试) (源代码)
- variable-supports-61.html (实时测试) (源代码)
- variable-supports-62.html (实时测试) (源代码)
- variable-supports-63.html (实时测试) (源代码)
- variable-supports-64.html (实时测试) (源代码)
- variable-supports-65.html (实时测试) (源代码)
- variable-supports-66.html (实时测试) (源代码)
- variable-supports-67.html (实时测试) (源代码)
作为通常 逗号省略规则 的一个例外,该规则要求在不分隔值时省略逗号,在 var() 中,必须将一个后面没有任何内容的裸逗号视为有效,表示一个空的回退值。
测试
注意:也就是说,var(--a,) 是一个有效的函数,它指定如果 --a 自定义属性无效或缺失,则应将 var() 替换为空。
var() 函数可用于元素中任何属性的值的任何部分。 var() 函数不能用作属性名称、选择器,或除属性值之外的任何其他内容。(这样做通常会产生无效语法,或者产生一个其含义与变量无关的值。)
测试
.foo{ --side : margin-top; var ( --side) :20 px ; }
这不等同于设置 margin-top: 20px;。相反,第二个声明因具有无效的属性名称而被直接作为语法错误丢弃。
函数的第一个参数是要替换的 自定义属性 的名称。函数的第二个参数(如果提供)是一个回退值,当所引用的 自定义属性 的值为 保证无效值 时,该回退值将作为替换值使用。
测试
注意:回退值的语法与 自定义属性 一样,允许使用逗号。例如,var(--foo, red, blue) 定义了一个 red, blue 的回退值;也就是说,第一个逗号和函数末尾之间的任何内容都被视为回退值。
没有回退时,应用程序作者必须为您组件使用的每个变量提供一个值。有了回退,组件作者可以提供默认值,这样应用程序作者只需要为他们想要覆盖的变量提供值。
/* 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 */ }
如果一个属性包含一个或多个 var() 函数,且这些函数在语法上是有效的,则必须假定整个属性的语法在解析时是有效的。只有在计算值时,即在 var() 函数被 替换 之后,才会进行语法检查。
测试
要在属性值中 替换 var()
- 如果 var() 函数的第一个参数所命名的 自定义属性 是 动画污染的,并且 var() 函数被用于一个 不可动画的 属性中,则在算法的剩余部分中将该 自定义属性 视为具有其初始值。
- 如果 var() 函数的第一个参数所命名的 自定义属性 的值不是初始值,则用相应 自定义属性 的值替换 var() 函数。
- 否则,如果 var() 函数具有作为其第二个参数的回退值,则用回退值替换 var() 函数。如果回退中存在任何 var() 引用,也请对它们进行 替换。
- 否则,包含 var() 函数的属性在 计算值时无效。
注意:其他事项也可能使属性在 计算值时无效。
测试
- css-variable-change-style-001.html (实时测试) (源代码)
- css-variable-change-style-002.html (实时测试) (源代码)
- variable-declaration-01.html (实时测试) (源代码)
- variable-declaration-02.html (实时测试) (源代码)
- variable-declaration-03.html (实时测试) (源代码)
- variable-declaration-04.html (实时测试) (源代码)
- variable-declaration-05.html (实时测试) (源代码)
- variable-generated-content-dynamic-001.html (实时测试) (源代码)
- variable-presentation-attribute.html (实时测试) (源代码)
- variable-reference-01.html (实时测试) (源代码)
- variable-reference-02.html (实时测试) (源代码)
- variable-reference-03.html (实时测试) (源代码)
- variable-reference-04.html (实时测试) (源代码)
- variable-reference-05.html (实时测试) (源代码)
- variable-reference-12.html (实时测试) (源代码)
- variable-reference-16.html (实时测试) (源代码)
- variable-reference-40.html (实时测试) (源代码)
- variable-reference-refresh.html (实时测试) (源代码)
- variable-substitution-background-properties.html (实时测试) (源代码)
- variable-substitution-basic.html (实时测试) (源代码)
- variable-substitution-filters.html (实时测试) (源代码)
- variable-substitution-replaced-size.html (实时测试) (源代码)
- variable-substitution-shadow-properties.html (实时测试) (源代码)
- variable-substitution-variable-declaration.html (实时测试) (源代码)
CSSOM(CSS 对象模型)
.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) *1 px ); }
测试
var() 函数在计算值时进行 替换。如果一个声明在所有 var() 函数被替换后不符合其声明的语法,则该声明在 计算值时无效。
测试
- variable-declaration-16.html (实时测试) (源代码)
- variable-declaration-17.html (实时测试) (源代码)
- variable-declaration-18.html (实时测试) (源代码)
- variable-declaration-19.html (实时测试) (源代码)
- variable-declaration-21.html (实时测试) (源代码)
- variable-transitions-transition-property-all-before-value.html (实时测试) (源代码)
- variable-transitions-value-before-transition-property-all.html (实时测试) (源代码)
如果一个声明在所有 var() 函数被替换后,仅包含一个 CSS 范围内的关键字(以及可能的空白字符),则其值确定方式就像该关键字一直作为其 指定值 一样。
:root{ --looks-valid : 20 px ; } p{ background-color : var ( --looks-valid); }
由于 20px 对于 background-color 来说是一个无效值,此属性实例计算为 transparent(background-color 的初始值)而不是其他值。
如果该属性是默认继承的属性(例如 color),它将计算为继承的值,而不是初始值。
p{ color : var ( --does-not-exist, initial); }
在上面的代码中,如果 --does-not-exist 属性不存在或在 计算值时无效,则 var() 将改为替换为 initial 关键字,使属性的行为就像它最初是 color: initial 一样。这将使其采用文档的初始 color 值,而不是默认继承,如果没有任何回退,本应如此。
3.1. 无效变量
当 自定义属性 的值为 保证无效值 时,var() 函数不能将其用于替换。尝试这样做会使声明在 计算值时无效,除非指定了有效的回退。
如果声明包含一个引用 自定义属性 且值为 保证无效值 的 var()(如上所述),或者它使用有效的 自定义属性,但属性值在替换其 var() 函数后无效,则该声明可能 在计算值时无效。发生这种情况时,计算值是以下之一,具体取决于属性的类型
- 属性是一个非注册的 自定义属性
- 属性是一个具有 通用语法 的 注册自定义属性
-
计算值为 保证无效值。
- 否则
-
属性的继承值或其初始值,取决于属性是否被继承,就好像属性的值已被指定为 unset 关键字一样。
:root{ --not-a-color : 20 px ; } p{ background-color : red; } p{ background-color : var ( --not-a-color); }
<p> 元素将具有透明背景(background-color 的初始值),而不是红色背景。如果 自定义属性 本身未设置,或包含无效的 var() 函数,也会发生同样的情况。
请注意这与如果作者直接在样式表中编写 background-color: 20px 的情况有何不同——那将是一个普通的语法错误,会导致规则被丢弃,因此会使用 background-color: red 规则。
注意:存在 在计算值时无效 的概念,是因为变量不能像其他语法错误那样“尽早失败”,所以当用户代理意识到属性值无效时,它已经丢弃了其他级联值。
3.2. 简写属性中的变量
在将 简写属性 解析为其组件长属性,以及从其组件长属性序列化 简写属性 时,var() 函数会产生一些复杂性。
如果 简写属性 的值中包含 var() 函数,则与其关联的 长属性 必须填充一个特殊的、对作者不可见的 待替换值,该值指示简写包含一个变量,因此在变量被 替换 之前,无法确定长属性的值。
然后,该值必须正常级联。在计算值时,即在 var() 函数最终被替换后,必须解析简写,并在此点赋予长属性适当的值。
测试
注意:当简写属性在没有 var() 的情况下编写时,它会在解析时被解析并分离为其组件 长属性;然后长属性参与 级联,而 简写属性 则或多或少被丢弃。然而,当简写包含 var() 时,则无法做到这一点,因为 var() 可以被任何内容替换。
待替换值 必须序列化为空字符串(如果 API 允许观察它们)。
测试
简写属性 通过收集其组件 长属性 的值并合成为一个能解析为同一组值的字符串来序列化。
如果给定 简写 的所有组件 长属性 都是来自同一原始简写值的 待替换值,则 简写属性 必须序列化为该原始(包含 var())值。
否则,如果给定 简写 的任何组件 长属性 是 待替换值,或者其自身包含尚未替换的 var() 函数,则 简写属性 必须序列化为空字符串。
3.3. 安全处理过长的变量
如果实现不当,var() 函数可用于“十亿笑话攻击”的变体
.foo {
--prop1 : lol;
--prop2 : var ( --prop1) var ( --prop1);
--prop3 : var ( --prop2) var ( --prop2);
--prop4 : var ( --prop3) var ( --prop3);
/* etc */
}
在这个简短的示例中,--prop4 的计算值为 lol lol lol lol lol lol lol lol,包含 8 个原始 lol 的副本。在此基础上每增加一个级别都会使标识符的数量翻倍;将其扩展到仅仅 30 个级别(手动几分钟的工作量),就会使 --prop30 包含 近十亿个 标识符实例。
为了避免此类攻击,UA 必须对 var() 函数展开成的标记流的允许长度实施 UA 定义的限制。如果 var() 展开后的标记流超过此限制,它反而会使它正在展开的属性在 计算值时无效。
本规范未定义应实施何种长度限制。然而,由于自定义属性存在包含一千字节或更多文本的有效用例,因此建议将限制设置得相对较高。
注意:UA 因资源限制而允许违反标准的一般原则在这里仍然普遍适用;UA 可能单独对它们能支持的自定义属性的长度或能支持的标识符的大小有限制。本节特别指出此攻击是因为其由来已久,并且它可以在所有片段初看时都似乎不大,但仍能被执行。
4. API
注意:自定义属性不会以驼峰命名形式出现在 CSSStyleDeclaration 对象上,因为它们的名称可能同时包含大写和小写字母,这表示不同的自定义属性。自动驼峰转换所执行的那种文本转换与此不兼容。它们仍然可以通过 getPropertyValue() 等通过其正确名称进行访问。
4.1. 序列化自定义属性
自定义属性名称必须序列化为作者提供的确切代码点序列,包括不改变大小写。
注意:对于非自定义属性,属性名称限制在 ASCII 范围内并且是 ASCII 大小写不敏感的,因此实现通常将名称小写化进行序列化。
自定义属性 的指定值必须序列化为 作者指定的确切值。其他属性中可能发生的简化,例如删除注释、规范化空格、从其值重新序列化数值标记等,不得发生。
自定义属性 的计算值也必须序列化为 作者指定的确切值,除了替换任何 var() 函数外。
测试
--y : /* baz */ ; --x : /* foo */ var ( --y) /* bar */ ;
--x 的指定值的序列化必须是 ,而 --x 的计算值的序列化必须是 。
(请注意,值开头的空格会自动被 CSS 解析器修剪;此处未保留。)
例如,将 UUID 存储在自定义属性中,如 --uuid: 12345678-12e3-8d9b-a456-426614174000,需要 UUID 在被脚本访问时按原样回显。
此值在技术上被 CSS 解析为一系列相邻的数字和维度。特别是,段 "-12e3" 被解析为一个数字,等于 -12000。按照 CSSOM 在其他上下文中要求的那样以该形式重新序列化它,将彻底破坏作者对该值的使用。
5. 变更
5.1. 自 2021 年 11 月 11 日 CR 草案以来的变更
-
明确了自定义属性适用于所有伪元素(包括那些具有限制属性列表的伪元素)
-
添加了示例以说明结合字符、连字等带来的问题
-
加强了关于使用不同代码点序列但外观相似的变量名称的措辞
-
通过使用视觉上更具区分度的语言(英语和希腊语)作为示例来阐明一个示例
-
将安全与隐私拆分为独立的章节
5.2. 自 2015 年 12 月 3 日 CR 以来的变更
-
既然 [css-syntax-3] 会自动从声明值中修剪空白字符,那么使 <declaration-value> 在自定义属性语法中变为可选,以便仍然允许空变量。(问题 774)
-
同样,使 var() 中的空回退变为有效。
-
'-' 属性保留供 CSS 将来使用。
-
添加了“动画污染”概念,以防止不可动画属性使用变量来引入动画能力。
-
定义了 保证无效值,使自定义属性的初始值以及循环或替换失败的结果更直观,并允许失败在替换过程中传播,直到最终被回退捕获。
-
定义了循环触发 计算值时无效 的行为。
-
允许变量解析为 CSS 范围内的关键字(仅通过作为回退提供才有可能)。
-
使带有 var() 的长属性也会触发其简写变为不可序列化,就像带有待替换值的长属性一样。
-
要求 UA 防御指数级替换攻击。
-
定义了如何序列化自定义属性的 值(之前仅指定了属性名称的序列化)。
5.3. 自 2014 年 5 月 6 日末次呼吁工作草案以来的变更
-
定义了当简写使用变量时长属性的序列化。
-
添加了对 DOM 的“大小写敏感”定义的链接。
-
添加了将变量与 :lang() 一起使用以进行简单 i18n 的示例。
-
明确了在自定义属性中使用 var() 必须符合 var() 语法。
6. 致谢
衷心感谢 CSS 工作组的几位成员多年来保持变量梦想的延续,特别是 Daniel Glazman 和 David Hyatt。感谢邮件列表上的多位人士帮助促成了这一变量实现的开发,特别是 Brian Kardell、David Baron、François Remy、Roland Steiner 和 Shane Stephens。
7. 隐私考量
本规范定义了一种纯粹作者级别的机制,用于在他们控制的页面内传递样式信息。因此,没有新的隐私注意事项。
8. 安全考量
§ 3.3 安全处理过长的变量 提到了一个可以针对“宏扩展”类机制(例如 var() 函数)进行的长期存在的拒绝服务攻击,并强制要求对此类攻击进行防御。