如何在 MPA 中实现向下展开动画,避免提前出现空白?
结论
要保留 cross-document view transition,需要让错误提示后面的内容也参与位置过渡。当前代码只展开了错误提示的快照,后续表单内容已经处于新页面的最终位置,所以提示下方的空白会先出现。
如果希望错误提示的实际高度逐帧增长,推动正常文档流中的后续内容,可以直接给 .incoming 添加动画,并取消这次导航的 View Transition。
为什么修改快照高度仍然会留下空白?
::view-transition-new(--incoming) 表示错误提示的新状态图像。它位于 View Transition 的独立绘制层,修改它的 block-size 不会重新排列原页面中后续字段的位置。
新页面布局时,真实的 .incoming 已经占据完整高度,后续字段也随之下移。提示图像却从零高度开始展开,因此提示下方的空间会提前出现。View Transitions 将文档状态变化与视觉过渡分开处理,具体机制见 CSS View Transitions 规范。
interpolate-size: allow-keywords 允许尺寸在数值与 auto 等关键字之间插值,但不会让快照动画改变真实文档流。参见 interpolate-size 定义。
方案一:让后续内容同步参与跨文档过渡
把所有需要随错误提示下移的内容放进同一个容器,并在导航前后的页面中给该容器设置相同的 view-transition-name。
没有错误提示的页面:
<main>
<h1>View transition MRE</h1>
<p><a href="/mre/with.html">Toggle</a></p>
<div class="form-rest">
<p>Lorem ipsum</p>
</div>
</main>
有错误提示的页面:
<main>
<h1>View transition MRE</h1>
<p><a href="/mre/without.html">Toggle</a></p>
<p class="incoming">Slides in</p>
<div class="form-rest">
<p>Lorem ipsum</p>
</div>
</main>
/mre/with.html 是示例路径,请换成实际页面地址。真实表单中,.form-rest 应包含提示之后所有需要移动的字段和按钮。
两页使用下面的共享 CSS,替换原来的过渡样式:
@view-transition {
navigation: auto;
}
:root {
interpolate-size: allow-keywords;
color-scheme: dark light;
}
body {
font-family: system-ui;
}
.incoming {
view-transition-name: --incoming;
}
.form-rest {
view-transition-name: form-rest;
}
::view-transition-image-pair(--incoming) {
overflow-y: clip;
}
@media (prefers-reduced-motion: no-preference) {
::view-transition-group(form-rest) {
animation-duration: 3s;
animation-timing-function: ease;
}
::view-transition-new(--incoming) {
animation: --slide-down-in 3s ease both;
}
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation-duration: 0s;
animation-delay: 0s;
}
}
@keyframes --slide-down-in {
from {
block-size: 0;
}
to {
block-size: auto;
}
}
浏览器会为前后页面中匹配的 form-rest 生成位置和尺寸过渡。错误提示展开的同时,后续内容的图像从旧位置移向新位置,看起来就像被提示逐渐向下推开。相关行为见 View Transition 分组伪元素定义。
后续内容的移动仍是在动画层中模拟的。如果提示出现时还有其他内容改变位置,也需要让这些内容参与相应的过渡,不能只处理其中一个字段。
方案二:直接动画真实元素的高度
如果只需要新页面中的错误提示展开,可以移除示例中的 @view-transition、view-transition-name 和相关伪元素规则,直接对 .incoming 应用动画:
:root {
color-scheme: dark light;
}
body {
font-family: system-ui;
}
.incoming {
block-size: auto;
margin-block: 0;
overflow: hidden;
}
@supports (interpolate-size: allow-keywords) {
@media (prefers-reduced-motion: no-preference) {
.incoming {
interpolate-size: allow-keywords;
animation: expand-error 350ms ease both;
}
}
}
@keyframes expand-error {
from {
block-size: 0;
}
to {
block-size: auto;
}
}
动画改变的是正常文档流中元素的高度,后面的字段会随布局逐帧下移。margin-block: 0 消除了示例中 <p> 默认的上下外边距,避免高度为零时仍留下额外间距。
将这段样式放在 <head> 中,或通过首屏样式表加载,可以避免元素先完整显示再开始动画。这个方案实现的是目标页面的入场展开,不负责连接两个页面的视觉状态。
注意事项
- 跨文档方案需要导航两端满足 View Transition 的启用条件;同一页面中,每个
view-transition-name必须唯一。 - 两个方案都依赖浏览器对相应 CSS 功能的支持。第二个方案通过
@supports保留静态显示作为回退,不支持时错误提示仍会正常出现。 - 如果提示增加了
padding、border或外边距,仅将内容高度设为零可能仍有占位。需要一并处理这些尺寸,或使用无边框、无内边距的外层容器负责展开。 - 示例中的跨文档动画保留
3s是为了便于观察,实际交互中可按需要缩短。真实高度动画会触发布局更新,复杂表单应在目标浏览器中检查流畅度。
备注:内容仅供参考。
