内联条件语句在CSS中的实现

原文信息: 查看原文查看原文

Inline conditionals in CSS?

- Lea Verou

上周,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);
        }
    }

    /* (其他变体) */
}

通常,我们需要在元素本身上设置的声明非常少,有时甚至只有一个。然而,即使只有一个也太多了,使得使用自定义属性对许多(可能大多数)更高级别的自定义属性用例来说变得不可行。因此,组件库最终诉诸于像pilloutlinesize等表现性属性。

虽然表现性属性乍一看可能看起来不错,甚至可能对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年,如果过程没有在任何点上停滞(像通常那样)。

我们所拥有的只是对这项特性的工作达成了共识。接下来的步骤是:

  1. 就特性的语法达成共识。关于语法的辩论通常需要很长时间,因为语法是每个人都有意见的领域。目前的辩论围绕:
  2. 规范这个特性。
  3. 获得第一次实现。通常这是最困难的部分。一旦有一个浏览器实现,其他人就更容易加入。
  4. 在所有主流浏览器中发货。

我有一个页面,我在那里跟踪我的一些标准提案,这应该有助于阐明每个步骤的时间表是什么样的。事实上,你可以特别跟踪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要掌握难得多。

分享于 2024-06-24

访问量 42

预览图片