四个新的 CSS 功能,可实现流畅的进入和退出动画
动作是任何数字体验的核心部分,可引导用户完成一次互动到下一次互动。但是,Web 平台上的流畅动画存在一些缺口。这些功能包括能够轻松为进入和退出动画添加动画效果,以及针对可关闭的元素(例如对话框和弹出式窗口)顺畅地从顶层添加动画效果。
为了填补这些缺口,Chrome 116 和 117 引入了四项新的网络平台功能,可针对独立属性提供流畅的动画和过渡效果。
这四项新功能包括:
- 能够在关键帧时间轴上为
display
和content-visibility
添加动画效果(从 Chrome 116 开始)。 - 带有
allow-discrete
关键字的transition-behavior
属性,用于支持转换display
等离散属性(从 Chrome 117 开始)。 @starting-style
规则,用于为从display: none
到顶层(从 Chrome 117 中)的进入效果添加动画效果。overlay
属性,用于控制动画播放期间的顶层行为(从 Chrome 117 开始)。## 在关键帧中显示动画
从 Chrome 116 开始,您可以在关键帧规则中使用 display
和 content-visibility
。这两个会在关键帧发生时对调。无需其他新值即可支持这一点:
.card {
animation: fade-out 0.5s forwards;
}
@keyframes fade-out {
100% {
opacity: 0;
display: none;
}
}
上面的示例以动画形式将不透明度在 0.5 秒内变为 0,然后将 display 设置为“none”。此外,forwards
关键字可确保动画保持其结束状态,以便应用它的元素保持 display: none
和 opacity: 0
状态。
这是一个简单的示例,它模拟了使用过渡时可以执行的操作(请参阅“过渡”部分中的演示)。不过,过渡无法创建更复杂的动画,如以下示例所示:
.card {
animation: spin-and-delete 1s ease-in forwards;
}
@keyframes spin-and-delete {
0% {
transform: rotateY(0);
filter: hue-rotate(0);
}
80% {
transform: rotateY(360deg);
filter: hue-rotate(180deg);
opacity: 1;
}
100% {
opacity: 0;
display: none;
}
}
spin-and-delete
动画是一种退出动画。首先,卡片将在 Y 轴上旋转,经过色调旋转,然后在 80%
处通过时间轴,将不透明度从 1 转换为 0。最后,卡会从 display: block
切换到 display: none
。
对于这些退出动画,您可以为动画设置一个触发器,而不是将其直接应用于元素。例如,通过将事件监听器附加到按钮,以触发类应用动画,如下所示:
.spin-out {
animation: spin-and-delete 1s ease-in forwards;
}
document.querySelector('.delete-btn').addEventListener('click', () => {
document.querySelector('.card').classList.add('spin-out');
})
上例现在的结束状态为 display:none
。在很多情况下,您需要更进一步,移除设置了超时的 DOM 节点,以便动画先播放完毕。
转换离散动画
与使用关键帧为离散属性添加动画不同,如需对离散属性进行转换,您需要使用 allow-discrete
过渡行为模式。
transition-behavior
属性
allow-discrete
模式可实现离散转换,它是 transition-behavior
属性的值。transition-behavior
接受两个值:normal
和 allow-discrete
。
.card {
transition: opacity 0.25s, display 0.25s;
transition-behavior: allow-discrete; /* Note: be sure to write this after the shorthand */
}
.card.fade-out {
opacity: 0;
display: none;
}
transition
简写形式也会设置此值,因此您可以省略该属性,并在每个过渡的 transition
简写形式末尾使用 allow-discrete
关键字。
.card {
transition: opacity 0.5s, display 0.5s allow-discrete;
}
.card.fade-out {
opacity: 0;
display: none;
}
如果要为多个离散属性添加动画效果,则需要在要添加动画效果的每个属性后面添加 allow-discrete
。例如:
.card {
transition: opacity 0.5s, display 0.5s allow-discrete, overlay 0.5s allow-discrete;
}
.card.fade-out {
opacity: 0;
display: none;
}
入口动画的 @starting-style
规则
到目前为止,本文已经介绍了退出动画,若要创建进入动画,您需要使用 @starting-style
规则。
使用 @starting-style
应用一种样式,这样,在元素在网页上打开之前浏览器就可以查询到该样式。这是“打开之前”状态(即播放动画的场景)。
/* 0. BEFORE-OPEN STATE */
/* Starting point for the transition */
@starting-style {
.item {
opacity: 0;
height: 0;
}
}
/* 1. IS-OPEN STATE */
/* The state at which the element is open + transition logic */
.item {
height: 3rem;
display: grid;
overflow: hidden;
transition: opacity 0.5s, transform 0.5s, height 0.5s, display 0.5s allow-discrete;
}
/* 2. EXITING STATE */
/* While it is deleting, before DOM removal in JS, apply this
transformation for height, opacity, and a transform which
skews the element and moves it to the left before setting
it to display: none */
.is-deleting {
opacity: 0;
height: 0;
display: none;
transform: skewX(50deg) translateX(-25vw);
}
现在,这些 TODO 列表项同时具有进入和退出状态:
为元素添加到顶层以及从顶层添加动画效果
如需为元素添加动画效果和将其移出顶层,请为“打开”状态指定 @starting-style
,以告知浏览器从何处添加动画效果。对于对话框,打开状态使用 [open]
属性定义。对于弹出式窗口,请使用 :popover-open
伪类。
一个简单的对话框示例可能如下所示:
/* 0. BEFORE-OPEN STATE */
@starting-style {
dialog[open] {
translate: 0 100vh;
}
}
/* 1. IS-OPEN STATE */
dialog[open] {
translate: 0 0;
}
/* 2. EXIT STATE */
dialog {
transition: translate 0.7s ease-out, overlay 0.7s ease-out allow-discrete, display 0.7s ease-out allow-discrete;
translate: 0 100vh;
}
在下一个示例中,进入和退出效果是不同的。从视口底部向上移动进入动画效果,将效果退出到视口顶部。此外,它还使用嵌套 CSS 编写,以实现更直观的封装。
为弹出式窗口添加动画效果时,请使用 :popover-open
伪类,而不是之前使用的 open
属性。
.settings-popover {
&:popover-open {
/* 0. BEFORE-OPEN STATE */
/* Initial state for what we're animating *in* from,
in this case: goes from lower (y + 20px) to center */
@starting-style {
transform: translateY(20px);
opacity: 0;
}
/* 1. IS-OPEN STATE */
/* state when popover is open, BOTH:
what we're transitioning *in* to
and transitioning *out* from */
transform: translateY(0);
opacity: 1;
}
/* 2. EXIT STATE */
/* Initial state for what we're animating *out* to ,
in this case: goes from center to (y - 50px) higher */
transform: translateY(-50px);
opacity: 0;
/* Enumerate transitioning properties,
including display and allow-discrete mode */
transition: transform 0.5s, opacity 0.5s, display 0.5s allow-discrete;
}
overlay
属性
最后,如需从顶层淡出 popover
或 dialog
,请将 overlay
属性添加到过渡列表中。popover
和 dialog
会转义祖先裁剪和转换,并将内容放在顶层。如果不过渡 overlay
,您的元素会立即恢复为经过剪裁、转换和覆盖的状态,并且您将不会看到过渡效果。
[open] {
transition: opacity 1s, display 1s allow-discrete;
}
请改为在过渡或动画中添加 overlay
,以便为 overlay
和其他地图项添加动画效果,并确保在添加动画效果时它始终位于顶层。这样看起来会更加顺畅。
[open] {
transition: opacity 1s, display 1s allow-discrete, overlay 1s allow-discrete;
}
此外,当您在顶层打开多个元素时,叠加层可帮助您控制进出顶层的平滑过渡。如需了解不同之处,请参见这个简单的示例。如果您在转出第二个弹出式窗口时没有将 overlay
应用到第二个弹出式窗口,它会先移出顶层,跳到另一个弹出式窗口之后,然后再开始转换。这种效果不是很流畅。
关于视图转换的注意事项
如果您要进行 DOM 更改(例如在 DOM 中添加和移除元素),另一种实现流畅动画的绝佳解决方案是视图过渡。以下是上面两个使用视图转换构建的示例。
在第一个演示中,视图转换将处理转换,而不是设置 @starting-style
和其他 CSS 转换。视图转换设置如下:
首先,在 CSS 中,为每张卡片提供单独的 view-transition-name
。
.card-1 {
view-transition-name: card-1;
}
.card-2 {
view-transition-name: card-2;
}
/* etc. */
然后,在 JavaScript 中,在视图过渡中封装 DOM 变更(在本例中为移除卡片)。
deleteBtn.addEventListener('click', () => {
// Check for browser support
if (document.startViewTransition) {
document.startViewTransition(() => {
// DOM mutation
card.remove();
});
}
// Alternative if no browser support
else {
card.remove();
}
})
现在,浏览器可以处理每张卡片的淡出并变形到新位置。
另一个非常实用的示例是添加/移除列表项演示。在这种情况下,您需要记得为创建的每张卡片添加唯一的 view-transition-name
。
总结
这些新的平台功能使我们离在 Web 平台上实现顺畅的进入和退出动画更近了一步。