发布时间:2025 年 9 月 22 日
当您启动视图过渡时,浏览器会自动截取带有 view-transition-name 标记的元素的前后快照。这些快照会在伪元素树中呈现。默认情况下,生成的树是“扁平”的。这意味着 DOM 中的原始层次结构会丢失,并且所有捕获的视图过渡组都是单个 ::view-transition 伪元素下的同级元素。
这种扁平树方法足以满足许多应用场景的需求,但有些样式应用场景无法通过它来实现。以下是在扁平树中可能会产生意外视觉效果的效果示例:
- 剪裁(
overflow、clip-path、border-radius):剪裁会影响元素的子元素,这意味着视图过渡组同级元素无法相互剪裁。 opacity、mask-image和filter:同样,这些效果旨在作用于树的完全光栅化图片,影响子项,而不是单独影响每个项。- 3D 转换(
transform-style、transform、perspective):为了显示全方位的 3D 转换动画,需要保持一定的层次结构。
以下示例展示了一个扁平伪树,其中包含被 DOM 树中的祖先元素剪裁的元素。这些元素在视图过渡期间会失去剪裁效果,从而导致视觉效果不佳。
嵌套视图过渡组是视图过渡的扩展,可让您将 ::view-transition-group 伪元素相互嵌套。嵌套视图过渡组后,便可在过渡期间恢复剪裁等效果。
Browser Support
从扁平伪树到嵌套伪树
在以下演示中,您可以点击某人的头像,查看有关该用户的更多信息。动画由同文档视图过渡处理,该过渡会将点击的按钮变形为对话框,在屏幕上移动头像和名称,并向上或向下滑动对话框中的段落。
实时演示
试唱录制
试唱录制(降速版)
如果您仔细观察演示,会发现过渡存在一个问题:尽管包含说明的段落是 DOM 中 <dialog> 元素的子元素,但在过渡期间,文本并未被 <dialog> 的框剪裁:
<dialog id="info_bramus" closedby="any">
<h2><img alt="…" class="avatar" height="96" width="96" src="avatar_bramus.jpg"> <span>Bramus</span></h2>
<p>Bramus is …</p>
<p>…</p>
</dialog>
对 <dialog> 应用 overflow: clip 也不会执行任何操作。
问题在于视图过渡构建和呈现其伪树的方式:
- 在伪树中,默认情况下,所有快照都是彼此的同级快照。
- 伪树会在
::view-transition伪元素中呈现,该伪元素会在整个文档的顶部呈现。
具体到此演示,DOM 树如下所示:
html
├─ ::view-transition
│ ├─ ::view-transition-group(card)
│ │ └─ ::view-transition-image-pair(card)
│ │ ├─ ::view-transition-old(card)
│ │ └─ ::view-transition-new(card)
│ ├─ ::view-transition-group(name)
│ │ └─ ::view-transition-image-pair(name)
│ │ ├─ ::view-transition-old(name)
│ │ └─ ::view-transition-new(name)
│ ├─ ::view-transition-group(avatar)
│ │ └─ ::view-transition-image-pair(avatar)
│ │ ├─ ::view-transition-old(avatar)
│ │ └─ ::view-transition-new(avatar)
│ ├─ ::view-transition-group(paragraph1.text)
│ │ └─ ::view-transition-image-pair(paragraph1.text)
│ │ └─ ::view-transition-new(paragraph1.text)
│ └─ ::view-transition-group(paragraph2.text)
│ └─ ::view-transition-image-pair(paragraph2.text)
│ └─ ::view-transition-new(paragraph2.text)
├─ head
└─ body
└─ …
由于 ::view-transition-group(.text) 伪元素是 ::view-transition-group(card) 伪元素的后续同级元素,因此它们会绘制在卡片顶部。
如需让 ::view-transition-group(card) 剪辑 ::view-transition-group(.text),::view-transition-group(.text) 伪元素应为 ::view-transition-group(card) 的子元素。为此,请使用 view-transition-group,以便为生成的 ::view-transition-group() 伪元素分配“父级组”。
如需更改父级群组,您有两种选择:
- 在父级上,将
view-transition-group设置为contain,使其包含所有具有view-transition-name的子级。 - 在所有子级上,将
view-transition-group设置为父级的view-transition-name。您还可以使用nearest定位到最近的祖先群组。
因此,对于此演示,若要使用嵌套视图过渡组,代码将变为:
button.clicked,
dialog {
view-transition-group: contain;
}
或
button.clicked,
dialog *,
view-transition-group: nearest;
}
添加此代码后,::view-transition-group(.text) 伪类现在会嵌套在 ::view-transition-group(card) 伪类中。这是在额外的 ::view-transition-group-children(…) 伪类中完成的,该伪类会将所有嵌套伪类放在一起:
html
├─ ::view-transition
│ ├─ ::view-transition-group(card)
│ │ ├─::view-transition-group-children(card)
│ │ │ ├─ ::view-transition-group(paragraph1.text)
│ │ │ │ └─ ::view-transition-image-pair(paragraph1.text)
│ │ │ │ └─ ::view-transition-new(paragraph1.text)
│ │ │ └─ ::view-transition-group(paragraph2.text)
│ │ │ └─ ::view-transition-image-pair(paragraph2.text)
│ │ │ └─ ::view-transition-new(paragraph2.text)
│ │ └─ ::view-transition-image-pair(card)
│ │ ├─ ::view-transition-old(card)
│ │ └─ ::view-transition-new(card)
│ ├─ ::view-transition-group(name)
│ │ └─ ::view-transition-image-pair(name)
│ │ ├─ ::view-transition-old(name)
│ │ └─ ::view-transition-new(name)
│ └─ ::view-transition-group(avatar)
│ └─ ::view-transition-image-pair(avatar)
│ ├─ ::view-transition-old(avatar)
│ └─ ::view-transition-new(avatar)
├─ head
└─ body
└─ …
最后,为了让 ::view-transition-group(card) 伪元素剪裁段落,请将 overflow: clip 应用于 ::view-transition-group-children(card) 伪元素:
::view-transition-group-children(card) {
overflow: clip;
}
结果如下:
实时演示
试唱录制
试唱录制(降速版)
仅当使用嵌套组时,::view-transition-group-children 伪类才会存在。它的大小与原始元素的边框盒相同,并具有与生成伪元素的元素(上例中的 card)形状和边框粗细相同的透明边框。
剪辑等
嵌套视图过渡组用于剪裁效果以外的位置。另一个例子是 3D 效果。在以下演示中,有一个选项可在过渡期间以 3D 形式旋转卡片。
html:active-view-transition-type(open) {
&::view-transition-old(card) {
animation-name: rotate-out;
}
&::view-transition-new(card) {
animation-name: rotate-in;
}
}
html:active-view-transition-type(close) {
&::view-transition-old(card) {
animation-name: rotate-in;
}
&::view-transition-new(card) {
animation-name: rotate-out;
}
}
如果没有嵌套视图过渡组,头像和名称不会随卡片一起旋转。
实时演示
试唱录制
试唱录制(降速版)
通过将头像和名称伪元素嵌套在卡片内,可以恢复 3D 效果。但您还需要执行其他操作。除了轮替 ::view-transition-old(card) 和 ::view-transition-new(card) 伪账号之外,您还需要轮替 ::view-transition-group-children(card) 伪账号。
html:active-view-transition-type(open) {
&::view-transition-group-children(card) {
animation: rotate-in var(--duration) ease;
backface-visibility: hidden;
}
}
html:active-view-transition-type(close) {
&::view-transition-group-children(card) {
animation: rotate-out var(--duration) ease;
backface-visibility: hidden;
}
}
实时演示
试唱录制
试唱录制(降速版)
更多演示
在以下示例中,嵌套视图过渡组用于确保卡片被其祖先滚动条剪裁。您可以使用包含的控件开启或关闭嵌套视图过渡组。
实时演示
试唱录制
此演示的有趣之处在于,所有 ::view-transition-group(.card) 伪元素都会嵌套在祖先 ::view-transition-group(cards) 伪元素中,并被其剪裁。#targeted-card 被排除在外,因为其进入/退出动画不应被 ::view-transition-group(cards) 剪裁。
/* The .cards wrapper contains all children */
.cards {
view-transition-name: cards;
view-transition-group: contain;
}
/* Contents that bleed out get clipped */
&::view-transition-group-children(cards) {
overflow: clip;
}
/* Each card is given a v-t-name and v-t-class */
.card {
view-transition-name: match-element;
view-transition-class: card;
}
/* The targeted card is given a unique name (to style the pseudo differently)
and shouldn't be contained by the ::view-transition-group-children(cards) pseudo */
#targeted-card {
view-transition-name: targeted-card;
view-transition-group: none;
}
回顾
嵌套视图过渡可让您在构建伪元素时保留 DOM 树的部分拓扑。这可以实现各种以前无法通过视图过渡实现的效果,其中一些效果已在本文中介绍。
嵌套会改变视图过渡的构建模型,旨在用于创建高级效果。如前所述,元素级视图过渡也可以通过更简单的模型实现部分效果。我们建议您尝试这两种功能,以确定哪种功能最符合您的需求。