CSSWG 会议后我感到兴奋的 CSS 新特性

Avatar of Juan Diego Rodríguez
Juan Diego Rodríguez 发布

DigitalOcean 提供适用于您旅程各个阶段的云产品。立即开始使用 $200 免费额度!

,CSS 工作组 (CSSWG) 在西班牙科鲁尼亚举行了今年的第二次面对面会议,议程中包含了大量即将添加到该语言的新特性和改进。如果说 2023 年带给了我们诸如开箱即用的 嵌套容器和样式查询 以及 has: 选择器 等令人难以置信的进步,那么 2024 年将以 更多 的突破性新增功能来填满,并且会 更加 充实。无论像 内联条件语句 这样的新特性刚刚起步还是长期项目即将完成,2024 年已经充满了令人兴奋的发展——而现在才 7 月!

我想分享一下我认为在会议中讨论的一些最有趣和最重要的即将添加到 CSS 的特性。但是,我并不希望您将以下内容视为对讨论的精确回顾。相反,我想提出在上次会议上受到关注的即将添加到 CSS 的更广泛的主题。实际上,所讨论的特性已经酝酿多年,讨论的重点是特定情况和新的增强功能,而不是定义整个规范;在一个会议中完成这样的工作是不可能的。

您可以在 CSSWG 会议议程 上查看讨论的具体问题。

特性 1:如果我们得到了 if() 会怎样?

自从 CSS 自定义属性 在 2016 年左右获得了可靠的支持以来,人们尝试了很多方法,希望能够根据自定义属性的值应用特定的样式,当然,不使用 JavaScript。最早的条件样式解决方法之一是 Roman Komarov 在 2016 年在 “CSS 变量的条件语句” 中发布的。从那时起,人们就记录了许多其他用于在 CSS 中进行条件声明的技巧(包括 CSS-Tricks 上 Ana Tudor 的这个极其巧妙的方法)。实际上,您可以在 CSSWG 成员 Lea Verou 最近的文章 “CSS 中的内联条件语句,现在?” 中找到一个完整的列表,它讨论并比较了这些解决方法。

可以肯定的是,社区一直渴望一种使用自定义属性来应用样式的条件方法。如今,我们拥有 样式查询 的规范,它能够完成这项任务,但它们也有一些与 浏览器支持 无关的限制。这些限制中最大的是什么?我们无法直接对查询的容器进行样式设置,因此我们需要在 HTML 中对该容器进行某种包装。

<div class="news-container" style="--variant: info">
  <p>Here is some good <strong>news</strong></p>
</div>

…除了编写样式查询之外

.news-container {
  container-name: news-container;
}

@container news-container style(--variant: info) {
  p {
    color: blue;
    border: 1px solid blue;
  }
}

if() 可能的样子

在 CSSWG 方面,早至 2018 年就有了 关于添加 if() 函数的讨论。直到今年的 ——是的,六年后——CSSWG 才决定开始为 CSS 开发 if()。尽管看起来不错,但不要指望在至少两年内在浏览器中看到 if()!(这是 Lea 非正式的估计。)我们可能需要更长时间才能获得足够的浏览器支持,才能在生产环境中可靠地使用它。规范草案才刚刚开始,很多事情需要先通过测试。作为参考,CSS 变量工作草案始于 2012 年,直到 2016 年才获得了广泛的浏览器支持。

在语法方面,if() 可能将借用 JavaScript 和其他编程语言的三元运算符,结构如下

if(a ? b : c)

…其中 a 是我们正在检查的自定义属性,bc 是可能的条件返回值。为了检查样式,将使用内联 style(--my-property: value)

.forecast {
  background-color: if(style(--weather: clouds) ? var(--clouds-color): var(--default-color));
}

即使 CSS 中没有使用 ?: 在其他地方也有不同的含义,我认为这种语法是大多数人熟悉的,更不用说它还允许无缝的条件链接。

.forecast {
  background-color: if(
    style(--weather: clouds) ? var(--clouds-color): 
    style(--weather: sunny) ? var(--sunny-color);
    style( --weather: rain) ? var(--rain-color): var(--default-color)
  );
}

未来的 if() 改进

虽然这些可能不会在初始版本中实现,但看到 if() 从现在到未来某个时间可能会发生的变化,还是很有趣的

  • 支持其他内联条件语句 我们应该使用 style() 查询来检查自定义属性,但我们也可以使用内联 media() 查询来检查媒体特性,或者使用内联 support() 来检查用户代理是否支持特定属性。
.my-element {
  width: if(media(width > 1200px) ? var(--size-l): var(--size-m));
}
  • 在其他 CSS 函数中使用条件语句 在未来的草案中,我们可能可以在其他函数中使用三元运算符,而无需将它们包装在 if() 中,例如,就像我们在 clamp()round() 函数中一样,可以进行计算而无需使用 calc()

特性 2:跨文档视图过渡

去年,视图过渡 API 使我们能够在网页和状态之间导航时创建无缝过渡。无需组件或框架,无需动画库——只需要纯 HTML 和 CSS,再加一点 JavaScript。视图过渡的第一个实现很久以前就烘焙到了浏览器中,但它基于 Chrome 定义的一个实验性函数,并且仅限于两个状态之间的过渡(单页视图过渡),不支持不同页面之间的过渡(即,多页视图过渡),而这正是我们大多数开发人员所渴望的。模拟原生应用程序行为的可能性令人兴奋!

这就是为什么 CSS 视图过渡模块级别 2 如此令人惊叹,也是为什么它是本文中我们涵盖的所有 CSS 新增功能中最喜欢的。是的,该特性带来了页面之间开箱即用的无缝过渡,但真正重要的是它消除了实现这一目标的框架需求。我们可以不使用库——比如 React + 一些路由库——而是退回到纯 CSS 和 JavaScript。

当然,在某些复杂程度上,视图过渡 API 可能力不从心,但对于我们只需要页面过渡而不需要框架带来的性能损耗的无数情况来说,它非常棒。

选择使用视图过渡

当我们在 相同来源 之间导航两个页面时,会触发视图过渡。在这种情况下,导航可能是点击链接、提交表单或使用浏览器按钮来回切换。相反,诸如在相同来源页面之间使用搜索栏之类的操作不会触发页面过渡。

两个页面——我们正在离开的页面和我们正在进入的页面——都需要使用 @view-transition at-rule 选择使用过渡,并将 navigation 属性设置为 auto

@view-transition {
  navigation: auto;
}

当两个页面都选择使用过渡时,浏览器会对两个页面进行“快照”,并将“之前”页面平滑地淡入“之后”页面。

在“快照”之间过渡

在该视频中,您可以看到旧页面如何淡入新页面,这是由于整个新伪元素树在过渡过程中持续存在,并使用 CSS 动画来产生这种效果。浏览器将使用唯一 view-transition-name 属性对具有唯一标识符的元素快照进行分组,该标识符可以作为参考,并被捕获在包含页面上所有过渡的 ::view-transition 伪元素中。

您可以将 ::view-transition 视为所有页面过渡的 :root 元素,它将页面过渡的所有部分分组到同一个默认动画中。

::view-transition
├─ ::view-transition-group(name)
│  └─ ::view-transition-image-pair(name)
│     ├─ ::view-transition-old(name)
│     └─ ::view-transition-new(name)
├─ ::view-transition-group(name)
│  └─ ::view-transition-image-pair(name)
│     ├─ ::view-transition-old(name)
│     └─ ::view-transition-new(name)
└─ /* and so one... */

注意,每个过渡都存在于一个 ::view-transition-group 中,它包含一个 ::view-transition-image-pair,而该 ::view-transition-image-pair 又由“旧”和“新”页面快照组成。我们可以根据需要添加任意数量的组,它们都包含一个包含两个快照的图像对。

快速示例:让我们使用 ::view-transition “根”作为参数来选择页面上的所有过渡,并在旧快照和新快照之间创建一个滑动动画。

@keyframes slide-from-right {
  from {
    transform: translateX(100vw);
  }
}

@keyframes slide-to-left {
  to {
    transform: translateX(-100vw);
  }
}

::view-transition-old(root) {
  animation: 300ms ease-in both slide-to-left;
}

::view-transition-new(root) {
  animation: 300ms ease-out both slide-from-right;
}

如果我们在页面之间导航,整个旧页面会滑到左边,而整个新页面会从右边滑进来。但我们可能希望阻止页面上的一些元素参与过渡,让它们在页面之间保持不变,而其他所有内容从“旧”快照移动到“新”快照。

这就是 view-transition-name 属性的关键所在,因为我们可以对某些元素进行快照,并将它们放在自己的 ::view-transition-group 中,与其他所有元素分开,以便单独处理。

nav {
  view-transition-name: navigation;

  /* 
    ::view-transition
    ├─ ::view-transition-group(navigation)
    │  └─ ::view-transition-image-pair(navigation)
    │     ├─ ::view-transition-old(navigation)
    │     └─ ::view-transition-new(navigation)
    └─ other groups...
  */
}

您可以在 GitHub 上 找到它的一个实时演示。请注意,在撰写本文时,浏览器支持 仅限于 Chromium 浏览器(即 Chrome、Edge、Opera)。

跨文档视图过渡有许多值得期待的地方。例如,如果我们有几个具有不同 view-transition-name 的元素,我们可以为它们赋予一个共享的 view-transition-class 来在一个地方为它们的动画设置样式——甚至可以使用 JavaScript 进一步定制视图过渡 来检查页面是从哪个 URL 过渡过来的,并相应地进行动画。

功能 3:锚点定位

在 CSS 中将一个元素相对于另一个元素定位似乎是那些不言而喻、简单明了的事情,但实际上需要将内插属性(topbottomleftright)与一系列魔术数字混合在一起,才能使事情变得正确。例如,获取一个悬停时出现在元素左侧的小工具提示,在 HTML 中可能看起来像这样

<p class="text">
  Hover for a surprise
  <span class="tooltip">Surprise! I'm a tooltip</span>
</p>

…以及使用当前方法的 CSS

.text {
  position: relative;
}


.tooltip {
  position: absolute;
  display: none;

  /* vertical center */
  top: 50%;
  transform: translateY(-50%);

  /* move to the left */
  right: 100%;
  margin-right: 15px; */
}

.text:hover .tooltip {
  display: block;
}

不得不改变元素的定位和内插值并不是世界末日,但这确实感觉应该有更简单的方法。此外,上一个例子中的工具提示非常脆弱;如果屏幕太小,或者我们的元素太靠左,那么工具提示将隐藏或溢出屏幕边缘。

CSS 锚点定位 是另一个在 CSSWG 会议中讨论的新功能,它有望让这种事情变得更加容易。

创建锚点

基本思想是,我们建立两个元素

  • 一个充当锚点,
  • 另一个是锚定到该元素的“目标”。

这样,我们有了一种更具声明性的方法来关联一个元素,并将其相对于锚定元素进行定位。

首先,我们需要使用新的 anchor-name 属性创建我们的**锚点**元素。

稍微改变一下我们的标记

<p>
  <span class="anchor">Hover for a surprise</span>
  <span class="tooltip">Surprise! I'm a tooltip</span>
</p>

我们为它赋予一个唯一的 dashed-indent 作为其值(就像自定义属性一样)

.anchor {
  anchor-name: --tooltip;
}

然后我们使用 position-anchor 属性将 .tooltip.anchor 相关联,并使用 fixedabsolute 定位。

.toolip {
  position: fixed;
  position-anchor: --tooltip;
}

.tooltip 目前定位在 .anchor 的顶部,但我们应该将其移到其他地方以防止这种情况。移动 .tooltip 最简单的方法是使用新的 inset-area 属性。让我们假设 .anchor 被放置在一个 3×3 网格的中间,我们可以通过为它分配一个行和列,在网格中定位工具提示。

Three by three grid with yellow element in the center tile labeled 'anchor'.

inset-area 属性为 .tooltip 在特定行和列上的网格中获取两个值。它使用物理值,如 leftrighttopbottom,以及根据用户书写模式的逻辑值,如 startend,以及共享的 center 值。它还接受引用 x 和 y 坐标的值,如 x-starty-end。所有这些值类型都是表示 3×3 网格上空间的方式。

例如,如果我们希望 .tooltip 相对于锚点的右上角进行定位,我们可以像这样设置 inset-area 属性

.toolip {
  /* physical values */
  inset-area: top right;

  /* logical values */
  inset-area: start end;

  /* mix-n-match values! */
  inset-area: top end;
}

最后,如果我们希望我们的工具提示跨越网格的两个区域,我们可以使用 span- 前缀。例如,span-top 将把 .tooltip 放在网格的顶部和中心区域。如果我们希望跨越整个方向,则可以使用 span-all 值。

Three by three grid with a yellow element in the center labeled 'anchor' surrounded by three tooltip examples demonstrating how tooltips are placed on the grid, including code examples for each example.

我们没有锚点的例子存在的问题之一是,工具提示可能会溢出屏幕。我们可以使用另一个新的属性来解决这个问题,这个属性称为 position-try-options,并与新的 inset-area() 函数结合使用。

(是的,有 inset-area 属性和 inset-area() 函数。这是我们必须记住的!)

position-try-options 属性接受一个逗号分隔的 .tooltip 在溢出屏幕外部时的回退位置列表。我们可以提供一个 inset-area() 函数列表,每个函数都包含与 inset-area 属性相同的值。现在,每次工具提示溢出屏幕外时,都会“尝试”下一个声明的位置,如果该位置导致溢出,就会尝试下一个声明的位置,以此类推。

.toolip {
  inset-area: top left;
  position-try-options: inset-area(top), inset-area(top right);
}

这是一个非常疯狂的概念,需要一段时间才能理解。CSSWG 成员 Miriam Suzanne 在一段值得观看的视频中坐下来,与 James Stuckey Weber 一起讨论和修改锚点定位。

如果您正在寻找一个 TL;DW,那么 Geoff Graham 对这段视频做了笔记

为了简洁起见,我们没有在这里介绍锚点定位的许多方面,特别是新的 anchor() 函数和 @try-position at-rule。anchor() 函数返回锚点边缘的计算位置,这为更精细地控制工具提示的内插属性提供了更多控制。@try-position at-rule 用于定义要设置在 position-try-options 属性上的自定义位置。

我的直觉是,使用 inset-area 对于绝大多数用例来说已经足够稳健。

CSSWG 是一个集体努力

之前我说过,这篇文章不会是 CSSWG 会议上讨论的精确转述,而是在新规范中对 CSS 的广泛介绍,由于它们的新颖性,这些规范注定会在这些会议中出现。甚至还有一些功能,我们根本没有时间在这篇综述中进行审查,而这些功能仍在讨论之中(咳嗽砌体布局)。

有一点是肯定的:规范不是在一次或两次会议的真空状态下制定的;它需要数十位优秀的作者、开发人员和用户代理的共同努力,才能将我们在日常 CSS 工作中使用的功能变成现实——更不用说我们在未来会使用的东西了。

我也有机会与 CSSWG 中的一些优秀的开发人员交谈,我发现他们从会议中得到的最大收获很有趣。您可能会期待 if() 在他们的清单上名列前茅,因为这是社交媒体上嗡嗡作响的东西。但 CSSWG 成员 Emilio Cobos 告诉我,例如,letter-spacing 属性本质上存在缺陷,而且没有简单的方法可以修复它,而这与 letter-spacing 目前由 CSS 定义并在浏览器中使用的方式相一致。其中包括将 普通属性转换为简写属性可能对代码库造成危害

我们可能会认为微不足道的每一个细节都会被仔细分析,以服务于网络和对网络的热爱。而且,正如我之前提到的,这些事情不会在封闭的真空状态下发生。如果您对 CSS 的未来有任何兴趣——无论是仅仅跟上它,还是积极参与其中——那么请考虑以下任何资源。