发布时间: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-image-pair(card)
│ │ │ ├─ ::view-transition-old(card)
│ │ │ └─ ::view-transition-new(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-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
伪元素。它的大小与原始元素的 border-box 相同,并具有透明边框,其形状和边框粗细与生成伪元素的元素(即上一个示例中的 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 树的部分拓扑。这可实现之前无法通过视图过渡实现的各种效果,我们在此处介绍了一些效果。
嵌套会更改视图过渡的构建模型,旨在用于创建高级效果。如上所述,元素范围的视图过渡也可以通过更简单的模型实现部分效果。建议您同时试用这两项功能,以便确定哪项功能最符合您的需求。