上周,CSS工作组(CSS WG)决定在CSS中加入内联if()
函数。这是什么意思,又为何令人兴奋?
上周,我们在西班牙的拉科鲁尼亚举行了一次CSS WG面对面会议。那次会议中有一个决议让我特别兴奋:一致同意在CSS中添加内联if()
函数。尽管我不是第一个提出内联条件语句的人,但我确实尝试将各种无休止的讨论缩减到一个最小可行产品(MVP),它可以快速实现,与实现者讨论想法,并最终发布了一个具体提案,并推动小组决议。相当诗意的是,相关讨论发生在我的生日那天,所以从某种意义上说,我得到了if()
这个最独特的生日礼物。😀
这也表明,提案被拒绝并不是某个特性的终结。实际上,一个特性在被接受之前被拒绝好几次是相当常见的:CSS嵌套、:has()
、容器查询都是一系列被拒绝提案中的最新迭代。if()
本身显然在2018年被拒绝,其语法与我提出的非常相似。有什么区别吗?风格查询已经推出,我们可以简单地引用相同的语法作为条件(加上来自Tab的@when
提案中的media()
和supports()
)。而在2018年的提案中,条件将如何工作在很大程度上尚未定义。
我在各种社交媒体上发布了这个消息,开发者的反响非常积极:
我甚至有来自大公司的朋友写信告诉我,他们的内部Slack因此而爆炸。这证明了我一直怀疑的事情,也是我向CSS WG提出的论点的一部分:这是一个巨大的痛点。希望积极的反应的数量和强度将有助于浏览器优先考虑这个特性,并尽早将其添加到他们的路线图中。
在所有这些平台上,除了最常见的《“我迫不及待地想看到这个特性推出!”》情绪外,还有一些其他反复出现的问题和相当多的困惑,我认为值得解决。
常见问题解答
if()是用来做什么的?它会取代风格查询吗?
恰恰相反 — if()
补充了风格查询。如果你可以用风格查询做某件事,请务必使用风格查询 — 它们几乎可以肯定是更好的解决方案。但是有些事情你根本无法用风格查询来做。让我解释一下。
动机用例是组件(在更广泛的意义上)通常需要定义更高级别的自定义属性,其值不仅在声明中逐字使用,而是在各种声明上设置不相关的值。
例如,考虑一个--variant
自定义属性(受到Shoelace的variant
属性的启发)。它可能是这样的:
--variant: success | danger | warning | primary | none;
这需要设置背景颜色、边框颜色、文本颜色、图标等。事实上,它的实际值在任何地方都没有逐字使用,它只用来设置其他值。
风格查询让我们走了一半的路:
.callout { /* 或在Shadow DOM中使用 :host */
@container (style(--variant: success)) {
&::before {
content: var(--icon-success);
color: var(--color-success);
}
}
/* (其他变体) */
}
然而,风格查询只适用于后代。我们不能这样做:
.callout {
@container (style(--variant: success)) {
border-color: var(--color-success-30);
background-color: var(--color-success-95);
&::before {
content: var(--icon-success);
color: var(--color-success-05);
}
}
/* (其他变体) */
}
通常,我们需要在元素本身上设置的声明非常少,有时甚至只有一个。然而,即使只有一个也太多了,使得使用自定义属性对许多(可能大多数)更高级别的自定义属性用例来说变得不可行。因此,组件库最终诉诸于像pill
、outline
、size
等表现性属性。
虽然表现性属性乍一看可能看起来不错,甚至可能对DX更好(字符更少 — 至少与为每个元素设置变量相比),但它们有几个可用性问题:
灵活性降低
它们不能根据选择器、媒体查询等条件应用。更改它们需要更多的JS。如果它们在另一个组件中使用,你就完蛋了,而使用(可继承的)自定义属性,你可以在父组件上设置属性,它会继承下来。
冗余
它们必须应用于单独的实例,并且不能继承。即使使用某种形式的模板或组件化来减少重复,它们在调试开发工具时仍然必须浏览这些属性。
缺乏一致性
由于几乎每个成熟的组件也支持自定义属性,用户必须记住哪种样式是通过属性完成的,哪种是通过自定义属性完成的。这种区别通常是任意的,因为它不是由用例驱动的,而是由实现方便性驱动的。
有了if()
,上述示例变得可能,尽管由于它不能级联(尽管我确实有一个提案允许它 — 以及所有其他IACVT声明):
.callout {
border-color: if(
style(--variant: success) ? var(--color-success-30) :
style(--variant: danger) ? var(--color-danger-30) :
/* (其他变体) */
var(--color-neutral-30)
);
background-color: if(
style(--variant: success) ? var(--color-success-95) :
style(--variant: danger) ? var(--color-danger-95) :
/* (其他变体) */
var(--color-neutral-95)
);
@container (style(--variant: success)) {
&::before {
content: var(--icon-success);
color: var(--color-success-05);
}
}
/* (其他变体) */
}
虽然这是主要用例,但事实证明,将媒体查询和支持条件作为if()
的条件语法的一部分也很容易。而且由于它是一个函数,它的参数(包括条件!)可以存储在其他自定义属性中。这意味着你可以这样做:
:root {
--xl: media(width > 1600px);
--l: media (width > 1200px);
--m: media (width > 800px);
}
然后定义值:
padding: if(
var(--xl) ? var(--size-3) :
var(--l) or var(--m) ? var(--size-2) :
var(--size-1)
);
就像JS中的三元运算符一样,对于只有一小部分值变化的情况,它也可能更符合人体工程学:
animation: if(media(prefers-reduced-motion) ? 10s : 1s) rainbow infinite;
那么它在浏览器中实现了吗?
信不信由你,这是我得到的一个真正的问题 😅。不,它还没有在浏览器中实现,而且还要一段时间。最乐观的估计是大约2年,如果过程没有在任何点上停滞(像通常那样)。
我们所拥有的只是对这项特性的工作达成了共识。接下来的步骤是:
- 就特性的语法达成共识。关于语法的辩论通常需要很长时间,因为语法是每个人都有意见的领域。目前的辩论围绕:
- 条件和分支之间使用什么分隔符?
- 如何表示没有值?我们是否允许像
var()
中那样使用空值(你可以做var(--foo,)
)或者我们是否引入一种专门表示“空值”的语法? - 最后一个值是否可选?
- 规范这个特性。
- 获得第一次实现。通常这是最困难的部分。一旦有一个浏览器实现,其他人就更容易加入。
- 在所有主流浏览器中发货。
我有一个页面,我在那里跟踪我的一些标准提案,这应该有助于阐明每个步骤的时间表是什么样的。事实上,你可以特别跟踪if()
的进展。
请注意,这些步骤不一定是线性的。通常我们规范一个初始版本,然后在语法不同的情况下进行更新和规范更新。有时浏览器甚至实现了早期的语法,然后它发生了变化,他们不得不改变他们的实现(就像嵌套发生的那样)
想让这发生得更快吗? 对if()
感到兴奋并想表达感激之情? 通过Open Collective资助我的网络标准工作。我在写这篇博客文章时开始这样做作为一种实验,并不打算大力推广它,但如果你喜欢,它就在那里。
这是CSS中的第一个条件吗?
许多回应是“哇,CSS终于有了条件!”
伙计们……CSS从一开始就有条件。每个选择器本质上都是一个条件!
此外:
@media
和@supports
规则是条件。别忘了@container
。var(--foo, fallback)
是一种有限类型的情况(本质上是if(style(--foo: initial) ? var(--foo) : fallback)
),这就是为什么它是大多数模拟内联条件的解决方案的基础。
这是否使CSS成为命令式?
一个普遍的误解是,非线性逻辑(条件,循环)使一种语言成为命令式的。
声明式与命令式不是关于逻辑,而是抽象级别的问题。 我们在描述目标还是如何实现它?在烹饪方面,食谱是命令式的,餐厅菜单是声明式的。
条件逻辑实际上可以使一种语言更声明式,如果它有助于更好地描述意图。
考虑以下两段CSS代码:
空间切换
button {
border-radius: calc(.2em + var(--pill, 999em));
}
.fancy.button {
/* 打开药丸 */
--pill: initial;
}
if()
button {
border-radius: if(style(--shape: pill) ? 999em : .2em);
}
.fancy.button {
--shape: pill;
}
我认为后者更声明式,即更接近于指定目标而不是如何实现它。
这是否使CSS成为一种编程语言?
一种非常常见的反应是关于CSS现在是否是一种编程语言(要么问它是否是,要么断言它现在是)。要回答这个问题,首先需要回答什么是编程语言。
如果它是图灵完备性使一种语言成为编程语言,那么CSS十多年前就已经成为编程语言了。但话说回来,Excel或Minecraft也是如此。那这到底意味着什么?
如果它是命令式,那么不,CSS不是编程语言。但许多真正的编程语言也不是!
但一个更深层次的问题是,为什么这很重要?是因为它可以合法化选择专门从事CSS吗?是因为即使你只写HTML和CSS,你也可以被认为是程序员吗?如果这只是出于外观考虑,那么我们应该从根本上解决问题,无论CSS是否是编程语言,都要合法化CSS专业知识。毕竟,任何了解几种备受尊敬的编程语言和CSS的人都知道,CSS要掌握难得多。