1. 引言
本节不具有规范性。
大型文档或应用程序 (甚至是小型的)可能包含相当多的CSS。 CSS文件中的许多值将是重复数据; 例如, 一个网站可能会建立一个颜色方案, 并在整个网站中重复使用三到四种颜色。 修改这些数据可能是困难且容易出错的, 因为它分散在CSS文件中 (并且可能跨多个文件), 可能不适合查找和替换。
此模块引入了一组自定义的作者定义属性,统称为自定义属性, 允许作者为具有自定义名称的属性分配任意值, 以及var() 函数, 允许作者在文档中的其他属性中使用这些值。 这使得阅读大型文件更容易, 因为看似任意的值现在有了信息丰富的名称, 并且使编辑这些文件更容易且不易出错, 因为只需在 自定义属性中更改一次值, 更改将自动传播到该变量的所有使用位置。
1.1. 值定义
本规范遵循CSS属性定义惯例,来自[CSS2],使用值定义语法,来自[CSS-VALUES-3]。 本规范未定义的值类型定义于CSS值与单位 [CSS-VALUES-3]。 与其他CSS模块的组合可能会扩展这些值类型的定义。
除了其定义中列出的特定属性值之外, 本规范中定义的所有属性 还接受CSS通用关键字作为其属性值。 为了可读性,它们没有被明确重复列出。
2. 定义自定义属性:--* 属性族
本规范定义了一组开放的属性,称为自定义属性, 其中,除了其他功能外,还用于定义替换值,供 var() 函数使用。
名称: | --* |
---|---|
值: | <declaration-value>? |
初始值: | 保证无效值 |
适用于: | 所有元素及所有伪元素(包括那些具有受限属性列表的伪元素) |
继承性: | 是 |
百分比: | 不适用 |
计算值: | 指定的值(变量替换后),或保证无效值 |
规范顺序: | 按语法顺序 |
动画类型: | 离散 |
用户代理预期在所有媒体上支持此属性,包括非可视化媒体。
自定义属性是任何名称以两个连字符(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; } /* 剩余的CSS文件 */ #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>我从根元素继承了蓝色!</p> <div>我直接设置了绿色!</div> <div id='alert' > 我直接设置了红色! <p>我也是红色的, 因为继承了!</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 属性更改时自动更新。
:root{ --one : calc ( var ( --two) +20 px ); --two : calc ( var ( --one) -20 px ); }
需要注意的是,自定义属性在计算值时解析其值中的 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; 也就是说,从第一个逗号到函数结束的内容都被视为回退值。
如果没有回退, 应用程序作者必须为组件使用的每个变量提供值。 通过使用回退,组件作者可以提供默认值, 这样应用程序作者只需要为他们想要覆盖的变量提供值即可。
/* 在组件的样式中: */ .component .header{ color : var ( --header-color, blue); } .component .text{ color : var ( --text-color, black); } /* 在大型应用程序的样式中: */ .component{ --text-color : #080; /* header-color 没有设置, 因此仍然是蓝色, 即回退值 */ }
如果某个属性包含一个或多个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
.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. Invalid Variables
当 自定义属性 的值是 保证无效值 时, 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);
/* 等 */
}
在此简短示例中,--prop4 的计算值为lol lol lol lol lol lol lol lol, 包含原始lol的 8 次重复。 每增加一级会使标识符的数量翻倍; 将其扩展到 30 级仅需几分钟的手工操作, --prop30将包含几乎十亿个标识符的实例。
为了避免此类攻击, 用户代理必须对 var() 函数展开的令牌流长度施加用户代理定义的限制。 如果 var() 展开到的令牌流超过此限制, 它会导致正在展开的属性在 计算值时间无效。
本规范未定义应施加的大小限制。 然而,考虑到自定义属性可能含有一千字节或更多文本的合法用例, 建议将限制设得相对较高。
注意:由于资源限制,用户代理可以违反标准的原则在此处依然适用; 用户代理可能分别对自定义属性的支持长度或标识符的支持长度有其他限制。 本节特别指出这种攻击, 因为其历史悠久, 并且在第一次检查时, 其组成部分似乎都不算太大。
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-wide关键字 (仅通过提供它作为回退时可能)。
-
使具有var()的长属性也会触发其短属性不可序列化, 就像具有挂起替换值的长属性已经这样做了一样。
-
要求用户代理防御指数替换攻击。
-
定义了如何序列化自定义属性的值 (以前只规定了属性名称的序列化)。
5.3. 自2014年5月6日最后一次呼叫工作草案以来的变更
-
定义了当简写属性使用变量时,长属性的序列化。
-
链接到DOM中“区分大小写”的定义。
-
添加了使用带有:lang()的变量进行简单国际化的示例。
-
澄清了自定义属性中使用var()必须符合var()语法规则。
6. 致谢
非常感谢CSS工作组的多位成员多年来对变量梦想的支持,特别是Daniel Glazman和David Hyatt。 感谢多个邮件列表成员为这一版本的变量开发做出的贡献,特别是 Brian Kardell, David Baron, François Remy, Roland Steiner, 和Shane Stephens。
7. 隐私考量
本规范定义了一种纯粹的作者级机制,用于在他们控制的页面内传递样式信息。 因此,没有新的隐私考量。
8. 安全考量
§ 3.3 安全处理过长变量提到了一个长期存在的 拒绝服务攻击,该攻击可以针对类似“宏展开”机制发起, 例如var()函数, 并要求采取防御措施。