PAYATHON 2026

如何在 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 是为了便于观察,实际交互中可按需要缩短。真实高度动画会触发布局更新,复杂表单应在目标浏览器中检查流畅度。

备注:内容仅供参考。