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-of
、text-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;
}
}
有关于容器查询更多的内容可以点击下面链接:
- 初探CSS容器查询
- 容器查询中的
container
和@container
- 容器查询给设计带来的变化
- 现代Web技术让我们离容器查询更近一步
- 下一代响应式 Web 设计:容器查询
- A Primer On CSS Container Queries
- CSS container queries are finally here
- Say Hello To CSS Container Queries
样式查询
上面示例所展示的只是查询父容器的尺寸,事实上,容器查询规范还允许你查询父容器的样式值。这在 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;
}
}
这只是样式查询的开端。未来,我们将拥有布尔查询来确定自定义属性值是否存在并减少代码重复,目前正在讨论的是范围查询,以根据一定范围的值应用样式。这将使得通过使用降雨概率或云量的百分比值来应用所示样式成为可能。
请注意,容器查询和样式查询的主要区别在于,容器查询用于查询容器尺寸大小,样式查询用于查询容器样式。
你可能会感到好奇,既然可以查询容器尺寸大小了,为什么还需要查询容器样式呢?
其实,在容器查询中,查询容器尺寸大小,允许我们根据组件的父容器(或祖先容器)的尺寸来控制组件样式,这非常有用。只不过,在某些情况下,我们可能不需要去查询容器尺寸大小,相反的是,我们想要查询容器的计算样式。那么,在这种情况之下,样式查询就会很有用处。
有关于这两者之间的差异和相关讨论,请移步 《现代 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)
相匹配:
上面你所看到的只是 :has()
最简单地使用,它可以帮助你做更为复杂的事情,甚至是一些带交互行为的操作。比如下面这个示例,使用 :has()
选择器和状态选择器,可以实现一个纯 CSS 制作的评分组件(StarRating):
也正因如此,我在《防御式 CSS 精讲》中,将 :has()
纳入到条件 CSS 的范畴,因为它在很多时候,能根据相关的动态条件,允许你使用不同的 CSS。
与 :has()
相关的教程:
- 条件 CSS 之
@
规则和 CSS 选择器 - CSS 的父选择器
:has()
- CSS 选择器
:is()
和:where()
与:has()
有什么功能 - 初探CSS 选择器Level 4
:has()
: the family selector- Level Up Your CSS Skills With The
:has()
Selector - Using
:has()
as a CSS Parent Selector and much more - CSS
:has()
- CSS
:has
Parent Selector
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
元素进行预过滤,然后从该列表中选择第二个元素。用下图来描述可能更为直观一些:
相关语法介绍,可以阅读:
text-wrap:balance
text-wrap
对我来说不陌生,但它取值 balance
,我还是很陌生的。前段时间在 Twitter 上看到 @Adam Argyle 分享了该特性:
text-wrap: balance
被号称是经典排版技术之一。它允许你可以在样式嵌入逻辑的另一个地方。从 Chrome 114 开始,你可以使用 text-wrap
属性和 balance
值来为标题使用文本换行平衡。
为了平衡文本,浏览器有效地执行二分查找,以找到不会导致额外行的最小宽度,停留在一个 CSS 像素上(而不是显示像素)。为了进一步最小化二分查找中的步骤,浏览器从平均行宽的 80%
开始。
简单地说,浏览器会自动计算单词的数量,并将它们平分在两行中。我们只需要应用 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;
}
动态视窗单位
我曾在《CSS 的黑魔法》一文中有提到,在 html
或 body
上设置 height
值为 100vh
时,在 iOS上的 Safari 存在一个长期存在且非常恼人的问题,它无法正确处理 vh
单位。例如,将容器设置为 100vh
实际上会导致元素略微太高: 移动端中的 Safari 在计算 100vh
时忽略了其 UI 的某些部分。
作为开发者,你希望 100vh
(视口高度的 100%
)意味着“与视口一样高”,但是 vh
单位无法考虑到移动设备上的导航栏缩回等问题,所以有时它会变得太长,导致滚动条的出现。
过去一般都是采用一些 Hack 手段来解决。但有了动态视窗单位之后,这一切都已再是问题。
- 小视口高度和宽度(
svh
和svw
),代表最小的活动视口尺寸。 - 大视口高度和宽度(
lvh
和lvw
),代表最大的尺寸。 - 动态视口高度和宽度(
dvh
和dvw
)。
动态视口单位的值会随着额外的浏览器动态工具栏的出现和消失而变化,比如顶部的地址栏或底部的选项卡栏。
更详细的阐述,请点击:
广色域颜色空间
Web 平台上的另一个重要新功能是广色域色彩空间。在广色域色彩在 Web 平台上可用之前,你可以用现代设备查看具有生动色彩的照片,但你无法获得与这些生动值相匹配的按钮、文本颜色或背景。
我们展示了一系列的图像,这些图像在广色域和窄色域之间进行了转换,以说明颜色的生动性及其效果。
但现在,Web 平台上有一系列新的颜色空间,包括 REC2020
、P3
、XYZ
、LAB
、OKLAB
、LCH
和 OKLCH
。在高清色彩指南中了解更多关于新的 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);
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%
);
}
在 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 的级联层(@layer)
- A Complete Guide to CSS Cascade Layers
- Cascade layers are coming to your browser
- Getting Started With CSS Cascade Layers
- An Introduction to CSS Cascade Layers
- Hello, CSS Cascade Layers
- Introduction to Cascade Layers in CSS
- Cascade Layers
- A Handy Use For Cascade Layers
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;
}
}
}
}
三角函数
CSS 中新增的三角函数是 CSS 数学函数的一部分。这些函数现在在所有现代浏览器中都已稳定,使你能够在 Web 平台上创建更有机的布局。一个很好的例子是这个径向菜单布局,现在可以使用 sin()
和 cos()
函数进行设计和动画制作。
在下面的演示中,点围绕中心点旋转。与其让每个点绕自己的中心旋转然后向外移动,不如在 X
和 Y
轴上转换每个点。在 X
和 Y
轴上的距离是通过考虑 --angle
的 cos()
和 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;
}
有关于 CSS 三角函数更多的内容:
- Trigonometric functions in CSS
- Improving CSS Shapes with Trigonometric Functions
- An Intro to Trigonometric CSS Functions
- Trigonometry in CSS and JavaScript: Introduction to Trigonometry
- Cyclical keyboard UX with a radio group and CSS trig functions
- Trigonometry in CSS and JS: A Series
单个变换属性
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 结构是声明性的,并且可以明确地编写,只需给弹出框元素指定一个 id
和 popover
属性即可。然后,将该 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
属性的元素不会强制关闭任何其他元素,不会立即接受焦点,也不支持轻松解除。它们通过切换或其他关闭操作来关闭。
锚点定位
弹出框也经常用于对话框和工具提示等元素中,这些元素通常需要锚定到特定的元素。以此事件为例。当你单击日历事件时,会出现一个对话框,显示你单击的事件附近的位置。日历项是锚点,弹出框是对话框,显示事件详细信息。
使用 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);
}
}
在这里你可以实现非常详细的设置,这可能会变得冗长,但这也是库和设计系统编写定位逻辑并在所有地方重复使用的机会。
在这篇博客文章中了解有关锚点定位的更多信息。
<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>
离散属性过渡
为了使所有这些弹出框平滑地进出过渡,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;
}
滚动驱动动画
滚动驱动动画允许你根据滚动容器的滚动位置控制动画的播放。这意味着当你向上或向下滚动时,动画会前进或后退。此外,通过滚动驱动动画,你还可以根据元素在其滚动容器中的位置来控制动画。这使你可以创建有趣的效果,例如视差背景图像、滚动进度条以及随着视图进入而显示的图像。
该 API 支持一组 JavaScript 类和 CSS 属性,允许你轻松创建声明性的滚动驱动动画。
要通过滚动驱动 CSS 动画,请使用新的 scroll-timeline
、view-timeline
和 animation-timeline
属性。要驱动 JavaScript Web Animations API,请将 ScrollTimeline
或 ViewTimeline
实例作为 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 提供的系列教程:
- Part 1: Introduction + Basic Scroll-Linked Animations
- Part 2: Scroll-Linked Animations with Element-based offsets
- Part 3: Practical Use-Cases
- Part 4: Scroll-Linked Animations With the Web Animations API (WAAPI)
- Animate elements on scroll with Scroll-driven animations
视图过渡
视图过渡 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 动态。