CSS In 2023

发布于 大漠

最近 Google I/O 大会开的是热火朝天,我也一直在关注与 Web UI 和 CSS 有关的话题。这不,在 @Una Kravets@Bramus@Adam Argyle 在 Chrome 开发者博客上就推出一篇关于这方面的文章,即 What's new in CSS and UI: I/O 2023 Edition。在这篇文章中介绍了有关于 Web UI 和 CSS 方面的近 20 个新特性,这 20 个新特性都是最近推出或即将推出的,而且是最令人兴奋和具有影响力的功能。那么,我以我自己的视角来加工一下,感兴趣的同学请继续往下阅读(你也可以点击这里跳转到原文进行阅读)。

往期回顾

@Adam Argyle 分别在 2020 年和 2021 年分享了 CSS 相关的新特性。你可以直接扫下图中的二维码获取相关清单:

在 2022 年,@Bramus 在 Twitter 上分享了 2022 年的将会出现在浏览器的 CSS 新特性:

作为一名 CSS 的爱好者,我也会跟进每年中新出的 CSS 特性,并整理成文,感兴趣的可以猛击下面相关的链接:

2023 年的 CSS

What's new in CSS and UI: I/O 2023 Edition》一文所列出的新特性清单有近 20 个,它们主要分四个方面的,即:

  • 与**下一代 Web 响应式** 有关的,比如容器查询、样式查询、:has() 伪类函数、nth-of 、经典排版技术(text-wrap: balance)、首字下沉(initial-letter)、动态视窗单位、广色域(Wide-gamut)颜色空间、color-mix() 函数 等 9 个;
  • CSS 基础原理 有关的,比如 CSS 的嵌套、层叠、作用域、三角函数 和单个变换属性等 5 个
  • 可定制组件 有关的,比如弹出层(Popover)、锚点定位、下拉选择菜单(<selectmenu>)和 离散属性过渡 等 4 个
  • 交互 有关的,比如滚动驱动的动画和视图转换等 2 个

在上清单中所列 CSS 新特性,其实早在以往分享的 CSS 特新性中早有出现,比如 容器查询、has() 伪类函数、动态视窗单位、首字下沉等。对于不怎么关注 CSS 的同学来说,这里所列清单都会感到是新的特性,但就我个人而言,近段时间接触的应该是 nth-oftext-wrap:balance 、广色区域颜色空间、三角函数、视窗转换以及可定制组件中的锚点定位、离散属性过渡等。

接下来,我们一起来了解一下这 20 个新特性。

注意,在这里不会对每个新特性都进行详细的阐述,在接下来的内容只是起一个抛砖引玉的作用,我会尽可能的在相应的特性中附上尽可能的资源链接,方便感兴趣的同学进一步深入的阅读和学习。

容器查询

CSS 容器查询(CSS Container Query) 的出现已经有几年了,只是最近,容器查询在所有现代浏览中已稳定了,不会有太多的新变化(了解过 CSS 容器查询的同学,应该都知道,它的语法规则都变了几次)。也就是说,你可以在现代浏览器中,使用容器查询特性来查询父元素的尺寸大小或样式,以确定用于其任何子元素的样式是什么。

它和我们熟知的 CSS 媒体查询之间的最大差异是,媒体查询只能访问并利用视窗的信息,这意味着它们只能在页面布局的宏观视图上工作。另一方面,容器查询是一种更精确的工具,可以支持任何数量的布局或嵌套布局。简而言之,媒体查询是一种用于宏布局布局的特性,而容器查询是一种用于微观布局的特性:

使用 CSS 容器查询可以做出很多有意思的东西,比如下面这个卡片组件:

特别声明,上面这个 Demo 来自于我的小册《现代 Web 布局》中的《下一代响应式 Web 设计:容器查询》一文。(Demo URL: https://codepen.io/airen/full/JjBRQvG)

关键的 CSS 代码如下:

.card {
    display: grid;
    grid-template-columns: 80px minmax(0, 1fr);
    grid-template-rows: auto;
    grid-template-areas:
        "figure  title"
        "figure  description";
    gap: 0.25rem 1rem;
    background-color: #fff;
    padding: 1rem;
    border-radius: 8px;
    box-shadow: 0 0 0.5em 0.5em rgb(0 0 0 / 0.125);
    color: #ce0063;
    align-items: center;
    align-content: center;
}
​
figure {
    grid-area: figure;
    border-radius: 50%;
    overflow: hidden;
    border: 2px solid currentColor;
    aspect-ratio: 1;
    padding: 4px;
}
​
.card h3 {
    grid-area: title;
    line-height: 1;
    font-size: 1.25rem;
    align-self: end;
}
​
.card p {
    grid-area: description;
    margin: 0;
    font-size: 90%;
    color: #797e8a;
    align-self: start;
}
​
.card ul {
    display: none;
    width: 100%;
    padding-top: 1rem;
    border-top: 3px solid;
}
​
.card svg {
    color: #ce0063;
    font-size: 48px;
}
​
.card__container {
    container-type: inline-size;
}
​
@container (width > 20rem) {
    .card {
        grid-template-columns: auto;
        grid-template-areas:
            "figure"
            "title"
            "description"
            "media";
        place-items: center;
        text-align: center;
        row-gap: 0.75rem;
    }
​
    figure {
        max-width: 160px;
    }
​
    .card ul {
        display: flex;
        grid-area: media;
        justify-content: space-evenly;
    }
​
    .card h3 {
        font-size: clamp(1.25rem, 2vw + 1.5rem, 1.75rem);
    }
}
​
@container (width > 35rem) {
    .card {
        grid-template-columns: 120px minmax(0, 1fr);
        grid-template-areas:
            "figure   title"
            "figure   description"
            "media    media";
        text-align: left;
        justify-items: start;
    }
}
​
@container (width > 45rem) {
    .card {
        grid-template-columns: 180px minmax(0, 1fr);
        grid-template-areas:
            "figure  title"
            "figure  description"
            "figure  media";
    }
  
    .card ul {
        justify-content: start;
        align-self: start;
        gap: 1rem;
    }
​
    .card svg {
        font-size: 24px;
    }
}

有关于容器查询更多的内容可以点击下面链接:

样式查询

上面示例所展示的只是查询父容器的尺寸,事实上,容器查询规范还允许你查询父容器的样式值。这在 Chrome 111 中目前部分实现,你可以使用 CSS 自定义属性来应用容器样式。

以下示例使用存储在自定义属性值中的天气特征,例如雨、晴和多云,来设置卡片的背景和指示器图标的样式。

.card-container {
  container-name: weather;
}

@container style(--rain: true) {
  .weather-card {
    background: linear-gradient(140deg, #76c4f2, #2ecdbb);
  }
  
  .weather-card:after {
    content: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGlo=);
    background: #3db2c3;
  }
}

@container style(--cloudy: true) {
  .weather-card {
    background: linear-gradient(-60deg, #909da4, #adc0be);
  }
  
  .weather-card:after {
    content: url(data:image/svg+xml;basz4K);
    background: #637685;
  }
}

@container style(--sunny: true) {
  .weather-card {
    background: linear-gradient(-30deg, #ffff75, #ffbc41);
  }
  
  .weather-card:after {
    content: url(data:image/svg+xml;b4K);
    background: #ffce17;
  }
}

@container style(--sunny: true) and style(--cloudy: true) {
    .weather-card {
      background: linear-gradient(24deg, #f0aec9, #a590ce);
    }
  
  .weather-card:after {
      content: url(data:image/svg+xml;=);
      background: #ba6ed9;
  }
}

Demo 地址:https://codepen.io/web-dot-dev/full/KKxzYQx

这只是样式查询的开端。未来,我们将拥有布尔查询来确定自定义属性值是否存在并减少代码重复,目前正在讨论的是范围查询,以根据一定范围的值应用样式。这将使得通过使用降雨概率或云量的百分比值来应用所示样式成为可能。

请注意,容器查询和样式查询的主要区别在于,容器查询用于查询容器尺寸大小,样式查询用于查询容器样式

你可能会感到好奇,既然可以查询容器尺寸大小了,为什么还需要查询容器样式呢?

其实,在容器查询中,查询容器尺寸大小,允许我们根据组件的父容器(或祖先容器)的尺寸来控制组件样式,这非常有用。只不过,在某些情况下,我们可能不需要去查询容器尺寸大小,相反的是,我们想要查询容器的计算样式。那么,在这种情况之下,样式查询就会很有用处。

有关于这两者之间的差异和相关讨论,请移步 《现代 Web 布局》中的《下一代响应式 Web 设计:容器查询》。

:has() 伪类函数

CSS 的 :has() 选择器被称为 CSS 的父选择器!它和 CSS 的容器查询特性一样,一直以来是 Web 开发者最想要的 CSS 功能。

就我个人而言,CSS 的 :has() 是最接近 if ... else ... 功能的。比如:

figure:has(> img) {
    padding: 0;
}

它的意思是,如果 figure 有子元素 img ,那么 figure 就指定一个 padding 值为 0 。它和 HTML 结构有紧密关联:

<figure>
    <img src="thumbnail.jpg" alt="" />
    <figcaption>An elephant at sunset</figcaption>
</figure>

<figure>
    <figcaption>An elephant at sunset</figcaption>
</figure>

<figure>
    <div class="media__object">
        <img src="thumbnail.jpg" alt="" />
    </div>
    <figcaption>An elephant at sunset</figcaption>
</figure>

上面三个结构,只有第一个结构才与 figure:has(> img) 相匹配:

Demo 地址:https://codepen.io/airen/full/YzOmgpm

上面你所看到的只是 :has() 最简单地使用,它可以帮助你做更为复杂的事情,甚至是一些带交互行为的操作。比如下面这个示例,使用 :has() 选择器和状态选择器,可以实现一个纯 CSS 制作的评分组件(StarRating):

Demo 地址:https://codepen.io/airen/full/poxeoeE

也正因如此,我在《防御式 CSS 精讲》中,将 :has() 纳入到条件 CSS 的范畴,因为它在很多时候,能根据相关的动态条件,允许你使用不同的 CSS。

:has() 相关的教程:

nth-of

大部分同学应该知道 :nth-child():nth-of-type() 这样的结构伪类选择器,但对于更高级的 nth-of 就不太知道了。

**注意,这里所说的 nth-of 是指 :nth-child():nth-of-type() 选择器更高级的用法,可以在它们的参数中带一个关键词 of ,允许你在更具体的子集中使用现有的微语法 An + B。比如 :nth-child(An+B [of S]?):nth-last-child(An+B [of S]?) 等。

如果你在特殊类上使用常规的 :nth-child(),例如 :nth-child(2),浏览器将选择应用特殊类的第二个子元素。这与 :nth-child(2 of .special)不同,后者将首先对所有 .special 元素进行预过滤,然后从该列表中选择第二个元素。用下图来描述可能更为直观一些:

Demo 地址: https://codepen.io/airen/full/YzJVpLQ

相关语法介绍,可以阅读:

text-wrap:balance

text-wrap 对我来说不陌生,但它取值 balance ,我还是很陌生的。前段时间在 Twitter 上看到 @Adam Argyle 分享了该特性:

text-wrap: balance 被号称是经典排版技术之一。它允许你可以在样式嵌入逻辑的另一个地方。从 Chrome 114 开始,你可以使用 text-wrap 属性和 balance 值来为标题使用文本换行平衡。

Demo 地址:https://codepen.io/web-dot-dev/pen/eYLwpRx

为了平衡文本,浏览器有效地执行二分查找,以找到不会导致额外行的最小宽度,停留在一个 CSS 像素上(而不是显示像素)。为了进一步最小化二分查找中的步骤,浏览器从平均行宽的 80% 开始。

Demo 地址:https://codepen.io/web-dot-dev/pen/KKxjpQm

简单地说,浏览器会自动计算单词的数量,并将它们平分在两行中。我们只需要应用 CSS text-wrap: balance 属性。

.c-hero__title {
    max-width: 36rem;
    text-wrap: balance;
}

这样,标题内容就会平衡,不会在某一行中有一个单词。

虽然这是今天可以尝试的一种很好的渐进增强,但需要注意的是,这个 API 仅适用于最多 4 行文本,所以它非常适合于标题和新闻标题,但对于更长的内容可能不是你所寻找的。

有关于这方面更详细的介绍:

首字下沉

Web 排版的另一个好的改进是 initial-letter。这个 CSS 属性可以更好地控制字首缩进的样式。

你可以在 ::first-letter 伪元素上使用 initial-letter 属性,指定字母的大小基于它占据的行数,字母块偏移量或“沉降”位置。

p:first-of-type:first-letter {
  font-family: "Merriweather", serif;
  initial-letter: var(--initial, 2 2);
  -webkit-initial-letter: var(--initial, 2 2);
  font-weight: bold;
  margin-right: 1rem;
  color: var(--indigo-8);
  text-shadow: 0.25rem 0.25rem purple;
}

Demo 地址:https://codepen.io/web-dot-dev/full/MWBErYp

动态视窗单位

我曾在《CSS 的黑魔法》一文中有提到,在 htmlbody 上设置 height 值为 100vh 时,在 iOS上的 Safari 存在一个长期存在且非常恼人的问题,它无法正确处理 vh 单位。例如,将容器设置为 100vh 实际上会导致元素略微太高: 移动端中的 Safari 在计算 100vh 时忽略了其 UI 的某些部分。

作为开发者,你希望 100vh(视口高度的 100%)意味着“与视口一样高”,但是 vh 单位无法考虑到移动设备上的导航栏缩回等问题,所以有时它会变得太长,导致滚动条的出现。

过去一般都是采用一些 Hack 手段来解决。但有了动态视窗单位之后,这一切都已再是问题。

  • 小视口高度和宽度(svhsvw),代表最小的活动视口尺寸。
  • 大视口高度和宽度(lvhlvw),代表最大的尺寸。
  • 动态视口高度和宽度(dvhdvw)。

动态视口单位的值会随着额外的浏览器动态工具栏的出现和消失而变化,比如顶部的地址栏或底部的选项卡栏。

更详细的阐述,请点击:

广色域颜色空间

Web 平台上的另一个重要新功能是广色域色彩空间。在广色域色彩在 Web 平台上可用之前,你可以用现代设备查看具有生动色彩的照片,但你无法获得与这些生动值相匹配的按钮、文本颜色或背景。

我们展示了一系列的图像,这些图像在广色域和窄色域之间进行了转换,以说明颜色的生动性及其效果。

但现在,Web 平台上有一系列新的颜色空间,包括 REC2020P3XYZLABOKLABLCHOKLCH。在高清色彩指南中了解更多关于新的 Web 颜色空间以及更多资讯。

同时,你可以立刻在 DevTools 中看到颜色范围如何扩展,那条白色线条代表 sRGB 范围结束的位置,宽色域颜色范围的开始位置。

还有很多可用于颜色的工具!也别错过所有精彩的渐变改进。甚至有一个全新的工具,Adam Argyle 构建的用于帮助你尝试新的 Web 色彩选择器和渐变构建器,可以在 gradient.style 上试用。

color-mix()

更进一步,与扩展的颜色空间有关的是 color-mix() 函数。该函数支持混合两种颜色值,以根据被混合的颜色通道创建新值。混合颜色的颜色空间会影响结果。使用更感知色彩空间(例如 oklch )进行工作,将会在比 srgb 更不同的颜色范围内运行。

color-mix(in srgb, blue, white);
color-mix(in srgb-linear, blue, white);
color-mix(in lch, blue, white);
color-mix(in oklch, blue, white);
color-mix(in lab, blue, white);
color-mix(in oklab, blue, white);
color-mix(in xyz, blue, white);

Demo 地址:https://codepen.io/web-dot-dev/pen/eYjKMVV

color-mix() 函数提供了一个长期请求的功能:在保留不透明颜色值的同时,添加一些透明度。现在,你可以在创建具有不同不透明度变化的颜色时使用你的品牌颜色变量。其中的一种方式是将颜色与透明混合。当你将品牌颜色混合到 10% 的透明度时,你将获得 90% 不透明的品牌颜色。你可以看到这如何帮助你快速构建颜色系统。

:root {
  --colorPrimary: hotpink;
  --colorPrimary-a10: color-mix(in srgb, var(--colorPrimary), transparent 90%);
  --colorPrimary-a20: color-mix(in srgb, var(--colorPrimary), transparent 80%);
  --colorPrimary-a30: color-mix(in srgb, var(--colorPrimary), transparent 70%);
  --colorPrimary-a40: color-mix(in srgb, var(--colorPrimary), transparent 60%);
  --colorPrimary-a50: color-mix(in srgb, var(--colorPrimary), transparent 50%);
  --colorPrimary-a60: color-mix(in srgb, var(--colorPrimary), transparent 40%);
  --colorPrimary-a70: color-mix(in srgb, var(--colorPrimary), transparent 30%);
  --colorPrimary-a80: color-mix(in srgb, var(--colorPrimary), transparent 20%);
  --colorPrimary-a90: color-mix(in srgb, var(--colorPrimary), transparent 10%);
  --colorSecondary: lime;
  --colorSecondary-a10: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 90%
  );
  --colorSecondary-a20: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 80%
  );
  --colorSecondary-a30: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 70%
  );
  --colorSecondary-a40: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 60%
  );
  --colorSecondary-a50: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 50%
  );
  --colorSecondary-a60: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 40%
  );
  --colorSecondary-a70: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 30%
  );
  --colorSecondary-a80: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 20%
  );
  --colorSecondary-a90: color-mix(
    in srgb,
    var(--colorSecondary),
    transparent 10%
  );
}

Demo 地址: https://codepen.io/web-dot-dev/full/BaqryZm

color-mix() 博客文章中,你可以查看更多示例和详细信息,或者您可以尝试 color-mix() playground 进一步学习

Playground URL: https://codepen.io/web-dot-dev/full/JjBZLrm

CSS 的嵌套

CSS 嵌套是 Sass 中受欢迎的功能之一,也是多年来 CSS 开发者最重要的需求之一,现在它终于加入到了 web 平台。嵌套允许开发者以更简洁的、分组的格式书写 CSS,从而减少冗余。

.card {}
.card:hover {}

/* can be done with nesting like */
.card {
  &:hover {
    
  }
}

你还可以嵌套媒体查询,这也意味着您可以嵌套容器查询。在下面的示例中,当容器中有足够的宽度时,将从纵向布局更改为横向布局:

.card {
  display: grid;
  gap: 1rem;

  @container (width >= 480px) {
    display: flex;
  }
}

当容器宽度有超过或等于 480px 时,布局调整为 flex。浏览器将在满足条件时简单地应用新的显示样式。

如需了解更多信息和示例,请查看我们的 CSS 嵌套文章

级联层

我们发现开发者面临的另一个问题是确保样式之间的优先级一致,而解决这个问题的一部分是更好地控制 CSS 级联。

级联层通过让用户控制哪些层比其他层具有更高的优先级来解决问题,这意味着可以更精细地控制何时应用你的样式。

比如:

/* Layer architecture */
/* Best practice to put @layer at top of the import file for organization */
@layer base,
       theme,
       layouts,
       components,
       utilities;

/* Base */
@import '../styles/base/normalize.css' layer(base);
@import '../styles/base/base.css' layer(base);
@import '../styles/base/theme.css' layer(theme); /* theme variables */
@import '../styles/base/typography.css' layer(theme); /* theme typography */
@import '../styles/base/utilities.css' layer(utilities);

/* Layouts */
@import '../styles/components/post.css' layer(layouts);

/* Components */

Demo 地址:https://codepen.io/web-dot-dev/project/editor/ZGQLkq

有关于级联层更多的介绍,可以阅读:

CSS 作用域

CSS 作用域样式允许开发者指定特定样式应用的边界,从而在 CSS 中创建本地命名空间。在此之前,开发者依靠第三方脚本来重命名类,或者采用特定的命名约定来防止样式冲突,但现在你可以使用 @scope

在这里,我们将 .title 元素的作用域限定为 .card。这将防止该标题元素与页面上的任何其他 .title 元素冲突,例如博客文章标题或其他标题。

@scope (.card) {
  .title { 
    font-weight: bold;
  }
}

你可以在此实时演示中查看将 @scope 与作用域限制和 @layer 一起使用的情况。

@layer demo, images, cards;

@layer cards {
  @scope (.card) to (> header > *, > figure > *, > footer > *) {
    :scope {
      display: grid;
      background: oklch(50% none none / 20%);
      border-radius: 10px;
      border: 1px solid oklch(50% none none / 20%);
      
      @media (prefers-color-scheme: light) {
        background: white;
        box-shadow: 0 30px 10px -20px oklch(0% none none / 25%);
      }

      > header {
        display: grid;
        gap: .5ch;
        padding: 2ch;
      }

      > article {
        max-inline-size: 50ch;
        line-height: 1.5;
        padding: 2ch 2ch 1ch;
      }

      > footer {
        display: flex;
        justify-content: flex-end;
        padding: 1ch 2ch;
        gap: 1ch;
      }
    }
  }
}

Demo 地址:https://codepen.io/web-dot-dev/full/MWPVGPL

三角函数

CSS 中新增的三角函数是 CSS 数学函数的一部分。这些函数现在在所有现代浏览器中都已稳定,使你能够在 Web 平台上创建更有机的布局。一个很好的例子是这个径向菜单布局,现在可以使用 sin()cos() 函数进行设计和动画制作。

在下面的演示中,点围绕中心点旋转。与其让每个点绕自己的中心旋转然后向外移动,不如在 XY 轴上转换每个点。在 XY 轴上的距离是通过考虑 --anglecos()sin() 分别确定的。

@property --angle {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: true;
}

:root {
    --angle: 0deg;
}

@keyframes adjust-angle {
    to {
        --angle: 360deg;
    }
}

:root {
    animation: adjust-angle linear 20s infinite paused;
}
:root:has(#enable_animation:checked) {
    animation-play-state: running;
}

.dot {
    --offset-per-child: calc(360deg / (var(--nth-siblings) + 1));
    --angle-offset: calc(var(--nth-child) * var(--offset-per-child));
        display: block;
    width: var(--tracksize);
    aspect-ratio: 1;
    border-radius: 50%;
    
    background: hsl(var(--angle-offset) 100% 50%);
    border: 1px solid #333;
    
    position: absolute;
    left: calc(50% - (var(--tracksize) / 2));
    top: calc(50% - (var(--tracksize) / 2));
    
    translate: calc(cos((var(--angle) + var(--angle-offset))) * var(--radius))
               calc(sin((var(--angle) + var(--angle-offset))) * var(--radius) * -1);
    
    pointer-events: none;
}

Demo 地址:https://codepen.io/web-dot-dev/full/ExePgOg

有关于 CSS 三角函数更多的内容:

单个变换属性

CSS 单个变换函数让开发者的工作效率不断提高。自上次举行 I/O 以来,各自的变换已经在所有现代浏览器中稳定。

在过去,你需要依靠变换函数来应用缩放、旋转和平移 UI 元素的子函数。这涉及到很多重复,尤其是在动画中多次应用不同的变换时,这样的操作非常令人沮丧。

.target {
  transform: translateX(50%) rotate(30deg) scale(1.2);
}

.target:hover {
  transform: translateX(50%) rotate(30deg) scale(2); /* 这里只有缩放被更改,但您必须重复所有其他部分 */
}

现在,通过分离变换类型并单独应用它们,你可以在 CSS 动画中拥有所有这些细节。

.target {
  translate: 50% 0;
  rotate: 30deg;
  scale: 1.2;
}

.target:hover {
  scale: 2;
}

通过这种方式,平移、旋转或缩放的更改可以在动画的不同时间以不同的变化率同时发生。

要了解更多信息,请阅读有关单个变换功能的文章

弹出框(Popover)

弹出框(Popover) API 为元素提供了一些内置的浏览器支持,例如:

  • 支持顶层,因此你无需管理 z-index。当您打开弹出窗口(popover)或对话框时,你将该元素提升到页面顶部的一个特殊层。
  • 自动弹出框(auto popovers)中免费提供轻松解除行为,因此当你单击元素之外时,弹出框被解除,从辅助功能树中移除,并正确管理焦点。
  • 弹出框目标与弹出框本身之间的默认辅助功能。

所有这些功能意味着无需编写太多 JavaScript 来创建所有这些功能和跟踪所有这些状态。

弹出框的 DOM 结构是声明性的,并且可以明确地编写,只需给弹出框元素指定一个 idpopover 属性即可。然后,将该 id 与将打开弹出框的元素同步,例如具有 popovertarget 属性的按钮:

<div id="event-popup" popover>
  <!-- Popover content goes in here -–>
</div>

<button popovertarget="event-popup">Create New Event</button> 

弹出框是一个用于 popover=auto 的快捷方式。具有 popover=auto 属性的元素将在打开时强制关闭其他弹出框,接受焦点,并可轻松解除。相反,具有 popover=manual 属性的元素不会强制关闭任何其他元素,不会立即接受焦点,也不支持轻松解除。它们通过切换或其他关闭操作来关闭。

Demo 地址:https://codepen.io/web-dot-dev/full/mdzpGwq

锚点定位

弹出框也经常用于对话框和工具提示等元素中,这些元素通常需要锚定到特定的元素。以此事件为例。当你单击日历事件时,会出现一个对话框,显示你单击的事件附近的位置。日历项是锚点,弹出框是对话框,显示事件详细信息。

使用 anchor() 函数可以创建一个居中的工具提示,使用锚点的宽度来将工具提示定位到锚点 x 位置的 50%。然后,使用现有的定位值来应用其余的放置样式。

但是,如果根据你所定位的方式,弹出框无法适应视口大小会发生什么?

为解决这个问题,锚点定位 API 包括你可以自定义的备用位置。以下示例创建了一个名为 top-then-bottom 的备用位置。浏览器将首先尝试将工具提示放在顶部,如果无法适应视口大小,则浏览器将在锚点元素下面定位它,并放在底部。

.center-tooltip {
  position-fallback: --top-then-bottom;
  translate: -50% 0;
}

@position-fallback --top-then-bottom {
  @try {
    bottom: calc(anchor(top) + 0.5rem);
    left: anchor(center);
  }

  @try {
    top: calc(anchor(bottom) + 0.5rem);
    left: anchor(center);
  }
}

Demo 地址:https://codepen.io/web-dot-dev/full/PoyRaZe

在这里你可以实现非常详细的设置,这可能会变得冗长,但这也是库和设计系统编写定位逻辑并在所有地方重复使用的机会。

在这篇博客文章中了解有关锚点定位的更多信息。

<selectmenu>

使用弹出框和锚点定位,可以构建完全可自定义的 selectmenu。OpenUI 社区小组一直在研究这些菜单的基本结构,并寻找允许自定义其内部任何内容的方法。以下是可视化的示例:

要构建最左侧的 selectmenu 示例,并使用与日历事件中的颜色相对应的彩色圆点,可以按以下方式编写它:

<selectmenu>
  <button slot="button" behavior="button">
    <span>Select event type</span>
    <span behavior="selected-value" slot="selected-value"></span>
    <span><img src="icon.svg"/></span>
  </button>
  <option value="meeting">
    <figure class="royalblue"></figure>
    <p>Meeting</p>
  </option>
  <option value="break">
    <figure class="gold"></figure>
     <p>Lunch/Break</p>
  </option>
  ...
</selectmenu>

Demo 地址:https://codepen.io/web-dot-dev/full/xxyWzJr

离散属性过渡

为了使所有这些弹出框平滑地进出过渡,Web 需要某种方式来动画离散属性。这些通常是以前不可动画化的属性,例如从顶层动画到另一层,从显示/隐藏状态动画。

为了实现弹出框、selectmenu 甚至像对话框或自定义组件这样的现有元素的良好过渡效果,浏览器正在启用新的管道来支持这些动画。

下面的弹出框演示演示了如何使用 :popover-open 以打开状态和 @initial 以打开之前的状态来动画弹出框的进出,并在打开后关闭状态时直接向元素应用变换值。为了使其与显示属性结合使用,需要将其添加到 transition 属性中,如下所示:

.settings-popover {
  &:popover-open {
    /*   0. before-change   */
    @initial {
      transform: translateY(20px);
      opacity: 0;
    }
    
    /*   1. open (changed) state   */
    transform: translateY(0);
    opacity: 1;
  }
  
  /*   2. After-change state */
  transform: translateY(-50px);
  opacity: 0;
  
  /*  enumarate transitioning properties, including display */
  transition: transform 0.5s, opacity 0.5s, display 0.5s;
}

Demo 地址:https://codepen.io/web-dot-dev/full/gOBGLBw

滚动驱动动画

滚动驱动动画允许你根据滚动容器的滚动位置控制动画的播放。这意味着当你向上或向下滚动时,动画会前进或后退。此外,通过滚动驱动动画,你还可以根据元素在其滚动容器中的位置来控制动画。这使你可以创建有趣的效果,例如视差背景图像、滚动进度条以及随着视图进入而显示的图像。

该 API 支持一组 JavaScript 类和 CSS 属性,允许你轻松创建声明性的滚动驱动动画。

要通过滚动驱动 CSS 动画,请使用新的 scroll-timelineview-timelineanimation-timeline 属性。要驱动 JavaScript Web Animations API,请将 ScrollTimelineViewTimeline 实例作为 timeline 选项传递给 Element.animate()

这些新 API 与现有的 Web Animations 和 CSS Animations API 协同工作,这意味着它们可以受益于这些 API 的优势。其中包括能够在主线程以外运行这些动画。是的,你现在可以通过一些额外的代码轻松地驱动滚动,使其在主线程以外流畅运行。有什么不喜欢的呢?!

使用 @scroll-timeline 规则和 animation-timeline 构建类似上面的效果,要以按下面三步来走:

/* (1) Define Keyframes */
@keyframes adjust-progressbar { 
    from { 
        transform: scaleX(0);
    } 
    to { 
        transform: scaleX(1); 
    } 
} 

/* (2) Define a ScrollTimeline */ 
@scroll-timeline scroll-in-document { 
    source: auto; 
    orientation: block; 
    scroll-offsets: 0, 100%; 
}

/* (3) Attach the Animation + set the ScrollTimeline as the driver for the Animation */ 
#progressbar { 
    animation: 1s linear forwards adjust-progressbar; 
    animation-timeline: scroll-in-document; 
}

有关于这方面的案例,可以点击这里获取

有关于这方面更详细的介绍,可以阅读 @Bramus 提供的系列教程:

视图过渡

视图过渡 API 使单步更改 DOM 变得容易,同时在两个状态之间创建动画过渡。这些可以是视图之间的简单淡出,但你还可以控制页面的各个部分如何过渡。

视图过渡可以用作渐进增强:将更新 DOM 的代码按任何方法进行分解,并使用视图过渡 API 包装它,以便在不支持该功能的浏览器中提供回退。

function spaNavigate(data) {
  // Fallback for browsers that don't support this API:
  if (!document.startViewTransition) {
    updateTheDOMSomehow(data);
    return;
  }

  // With a transition:
  document.startViewTransition(() => updateTheDOMSomehow(data));
}

过渡的外观应如何是由 CSS 控制的。

@keyframes slide-from-right {
  from { opacity: 0; transform: translateX(75px); }
}

@keyframes slide-to-left {
  to { opacity: 0; transform: translateX(-75px); }
}

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

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

正如 Maxi Ferreira 的这个精彩演示所展示的那样,其他页面交互,例如正在播放的视频,在进行视图过渡时仍然可以继续工作。

视图过渡目前从 Chrome 111 开始支持单页面应用程序 (SPAs)。多页面应用程序的支持正在进行中。如需了解更多信息,请查看我们完整的视图过渡指南,以引导你完成所有步骤。

小结

developer.chrome.com 上了解 CSS 和 HTML 的最新动态,并查看 I/O 视频以了解更多 Web 动态。