最近,CSS 工作组(CSSWG)决定在 CSS 值模块 Level 5 规范中添加 if() 条件语句,这可是个大新闻!
Lea Verou 在同一天的 X 平台上发布了相关的帖子,引起了我的注意。Lea 也是 提出该 GitHub 问题 的人,巧合的是,这个提案在她生日那天通过了。真是太巧了,这一天一定充满了惊喜! 你生日收到什么礼物了? “哦,你知道的,就是被 CSS 规范采纳的一个提案。” 太棒了!
这个提案通过意味着 CSSWG 将开始着手研究 if() 条件语句,并希望最终将其作为推荐的 CSS 功能。当然,想要正式落地还需要一段时间,甚至还可能中途夭折。
不过,根据条件应用样式的想法非常令人兴奋,值得我们提前了解。Lea 在她 X 平台的帖子发布当天,也在博客上 发布了一些关于这个话题的笔记,我也来总结一下,并补充一些新发现的细节。
这并不是一个新想法
很多提案都是从之前被拒绝的提案中诞生的,if() 也不例外。实际上,近年来我们已经获得了几个允许进行条件样式的 CSS 功能,例如 [:has()](https://css-tricks.com/almanac/selectors/h/has/) 和 容器样式查询 就是两个比较明显的例子。Lea 甚至还引用了 2018 年的一个 issue,内容和通过的提案非常相似。
区别在哪?
> 样式查询已经发布,我们可以简单地引用相同的语法来进行条件判断(加上 Tab 的 @when 提案 中的 media() 和 supports()),而在 2018 年的提案中,条件如何工作尚不明确。
> Lea Verou, “CSS 中的内联条件语句?
我喜欢 Lea 指出,CSS 从一开始就是一门条件式语言:
> 大家… CSS 从一开始就有了条件语句! 每一个选择器本质上都是一个条件语句!
> Lea Verou, “CSS 中的内联条件语句?
确实!级联样式表就是用来评估选择器,并将它们与页面上的 HTML 元素进行匹配。if() 带来的优势是可以让我们用选择器编写内联条件语句。
语法
简单来说,if() 语句的语法如下:
<if()> = if( <container-query>, [<declaration-value>]{1, 2} )
... 其中:
- 值可以嵌套,生成多个分支。
- 如果没有提供第三个参数,它就等同于一个空的标记流。
目前,所有这一切都还处于概念阶段,一切尚未确定。随着 CSSWG 对该功能的完善,我们可能会看到一些变化。但就目前而言,这个想法似乎围绕着指定一个条件,并设置两种声明的样式之一——一种作为“默认”样式,另一种作为条件匹配时应用的“更新”样式。
.element {
background-color:
/* 如果样式声明了以下自定义属性:*/
if(style(--variant: success),
var(--color-green-50), /* 匹配条件 */
var(--color-blue-50); /* 默认样式 */
);
}
在这个例子中,我们寻找一个 style() 条件,其中一个名为 --variant 的 CSS 变量被声明,并且被设置为 success 值,然后:
- …如果
--variant被设置为success,我们将success的值为--color-green-50,它映射到某个绿色的颜色值。 - …如果
--variant没有被设置为success,我们将success的值为--color-blue-50,它映射到某个蓝色的颜色值。
默认样式是可选的,所以我觉得在某些情况下可以省略,使代码更易读:
.element {
background-color:
/* 如果样式声明了以下自定义属性:*/
if(style(--variant: success),
var(--color-green-50) /* 匹配条件 */
);
}
上面的语法定义提到,除了匹配条件和默认样式之外,我们还可以支持第三个参数,它允许我们在条件中嵌套条件:
background-color: if(
style(--variant: success), var(--color-success-60),
if(style(--variant: warning), var(--color-warning-60),
if(style(--variant: danger), var(--color-danger-60),
if(style(--variant: primary), var(--color-primary)
)
),
)
);
哇! 看起来像是层层嵌套的结构! Lea 还提出了一种语法,可以使代码结构更扁平:
<if()> = if(
[ <container-query>, [<declaration-value>]{2} ]#{0, },
<container-query>, [<declaration-value>]{1, 2}
)
换句话说,嵌套条件更扁平,因为它们可以 在 初始条件 之外 进行声明。 概念与之前相同,但语法不同:
background-color: if(
style(--variant: success), var(--color-success-60),
style(--variant: warning), var(--color-warning-60),
style(--variant: danger), var(--color-danger-60),
style(--variant: primary), var(--color-primary)
);
所以,我们不再需要在一个 if() 语句中嵌套另一个 if() 语句,而是可以将所有可能的匹配条件都放到一个语句中。
这与样式查询有关
我们试图通过查询元素的样式来匹配 if() 条件。对于查询尺寸,没有对应的 size() 函数;容器查询隐式地假设了尺寸:
.element {
background: var(--color-primary);
/* 条件 */
@container parent (width >= 60ch) {
/* 应用的样式 */
background: var(--color-success-60);
}
}
当我们调用 style() 函数时,容器查询就变成了 样式 查询:
.element {
background: orangered;
/* 条件 */
@container parent style(--variant: success) {
/* 应用的样式 */
background: dodgerblue;
}
}
当在 if() 的上下文中查看时,样式查询对我来说更有意义。 如果没有 if(),人们很容易质疑样式查询的通用性。 但从这个角度来看,很明显,样式查询是更大图景的一部分,超越了容器查询本身。
关于 if() 语法,还有很多值得探讨的地方。例如,Tab Atkins 描述了一个可能导致混淆的场景,可能会导致匹配条件和默认样式参数之间的混淆。 所以,谁知道最终会是什么结果呢!
条件支持其他条件
正如我们已经提到的,if() 远非 CSS 中唯一提供的条件检查类型。如果我们编写一个内联条件语句来检查其他条件,例如 @supports 和 @media,会是什么样子呢?
代码如下:
background-color: if(
supports( /* 等 */ ),
@media( /* 等 */ )
);
挑战在于容器支持尺寸查询。如前所述,没有明确的 size() 函数,它更像是一个匿名函数。
@andruud 在 GitHub 讨论中 简洁地描述了这个挑战:
我不理解为什么我们不能使用
supports()和media(),但尺寸查询会造成布局上的循环,这很难或不可能检测到。(这就是我们一开始需要对尺寸容器查询进行限制的原因。)
“我们不能用 [X] 方法做到吗?”
当我们之前查看语法时,你可能注意到,if() 既与自定义属性有关,也与条件语句有关。 多年来,我们已经开发出了许多变通方法来模拟我们通过条件设置自定义属性值所能获得的功能,包括:
- 使用自定义属性作为布尔值,根据它是否等于
0或1来应用样式。 (Ana 有一篇关于这方面的精彩文章) - 使用一个空值的占位符自定义属性,当另一个自定义属性被设置时,它也会被设置,即 “自定义属性切换技巧”,就像 Chris 描述的那样。
- 容器样式查询! 问题是(除了缺乏实现之外),容器只将样式应用于其后代,即,它们在满足特定条件时不能将样式应用于自身,只能应用于其内容。
Lea 在另一篇名为 “CSS 中的内联条件语句,现在?” 的文章中深入探讨了这个问题,文章中包含一个表格,列出了并比较了各种方法,我将在下面直接粘贴出来。 这些解释充满了复杂的 CSS 专业知识,但对于理解 if() 的需求以及它与我们多年来使用的巧妙“技巧”相比如何,非常有用。
| 方法 | 输入值 | 输出值 | 优点 | 缺点 |
|---|---|---|---|---|
| 二进制线性插值 | 数字 | 定量 | 可以用作值的一部分 | 输出范围有限 |
| 切换 | var(--alias) (实际值太奇怪,无法直接暴露) | 任何 | 可以用作值的一部分 | 需要别名化一些奇怪的值 |
| 暂停动画 | 数字 | 任何 | 正常的、分离的声明 | 占用 animation 属性 |
| 级联怪异 | ||||
| 类型磨合 | 关键字 | syntax 描述符支持的任何值 | 高度灵活的暴露 API良好的封装 | 必须将 CSS 插入到 light DOM 中 |
| 繁琐的代码(尽管可以用构建工具自动化) | ||||
| Firefox 不支持(尽管这种情况正在 改变) | ||||
| 可变动画名称 | 关键字 | 任何 | 正常的、分离的声明 | 由于名称冲突,在 Shadow DOM 之外不切实际 |
占用 animation 属性 | ||||
| 级联怪异 |
祝你生日快乐,Lea!
虽然迟了两周,但还是要感谢你与我们分享你生日的喜悦!🎂
参考
- 自定义属性上的内联条件语句的 MVP 是什么? (Lea Verou, GitHub 问题 #10064)
- CSS 中的内联条件语句? (Lea Verou)
- CSS 中的内联条件语句,现在? (Lea Verou)