题干与适用场景
产品希望在 SPA 路由切换和图片详情展开时加入 View Transition API。请说明如何实现,并处理旧浏览器、prefers-reduced-motion、异步数据、焦点管理、动画失败和性能回退。
这道题考察 API 边界和渐进增强,而不是只展示一段动画代码。MDN 将 document.startViewTransition() 定义为同文档视图更新入口,并说明回调完成后才开始过渡;跨文档过渡还需要同源页面和 CSS @view-transition。动画必须服务状态变化,不能阻塞可用性。
面试官考察点
第一项是能否区分同文档 SPA、元素范围和跨文档过渡。第二项是能否处理不支持 API、回调拒绝、用户减少动态效果和页面离开等情况。第三项是能否保证焦点、语义 DOM、滚动和真实交互不被快照动画掩盖。
回答前需要澄清的问题
- 过渡发生在哪个边界? 是同文档 DOM 更新、单个元素,还是跨文档导航?
- 更新是否包含异步数据? 过渡前要等待什么,最长等待多久?
- 旧浏览器的可接受体验是什么? 无动画也必须完成同一状态更新。
- 哪些用户应减少或关闭动画?
prefers-reduced-motion和应用设置如何共同决定? - 页面状态如何恢复? 焦点、滚动位置、表单输入和返回导航是否保留?
- 性能预算是什么? 截图范围、动画时长、低端设备和并发过渡如何限制?
30 秒回答框架
“我先把状态更新和动画分开:支持 View Transition 时,用 startViewTransition 包住同步 DOM 更新;不支持时直接更新。异步数据先由路由或组件准备,回调只提交已准备好的状态,并监听 ready、finished 和 skipTransition。CSS 用命名视图控制范围,prefers-reduced-motion 下缩短或关闭动画。更新后恢复焦点与滚动,动画失败不能阻塞交互。最后用长任务、截图数量、完成时间和回退率验证低端设备表现。”
分步骤深入解答
第一步:先定义状态更新和动画目标
明确动画连接的是哪两个状态,例如列表卡片到详情页、筛选结果到新列表,而不是让整页无差别淡入。状态更新必须在没有动画时也正确完成,动画只是增强层。为每种导航类型定义允许的过渡名称和回退行为。
第二步:检测能力并保留同步回退
调用前检查 document.startViewTransition。不支持时直接执行同一个更新函数,不要复制两套业务逻辑。新浏览器支持并不代表所有旧设备都支持,MDN 的兼容性信息仍要求准备降级路径。
第三步:控制回调和异步数据边界
updateCallback 在旧视图快照后执行,返回的 Promise 完成后才进入下一帧。数据获取应尽量在调用前完成;若必须在回调中等待,要设置超时并允许跳过动画。回调拒绝时保持业务错误处理,不让半更新状态进入页面。
const update = () => renderState(nextState);
if (!document.startViewTransition || reduceMotion) {
update();
} else {
const transition = document.startViewTransition(update);
transition.finished.catch(() => {
// 动画失败不回滚已完成的状态更新
});
}第四步:用命名视图限制截图范围
只给需要共享位置的元素设置唯一 view-transition-name,避免为整棵大 DOM 树创建快照。列表中重复组件不能共享同一个名称,否则会产生歧义;动态列表要在更新前清理旧名称,并在更新后为新元素设置稳定名称。
第五步:实现减少动态效果和无障碍行为
监听 prefers-reduced-motion: reduce,把动画时长设为零或极短,并保留状态变化。动画期间不要隐藏键盘焦点或依赖颜色变化传达结果;更新完成后把焦点放到新视图的标题或等价位置。web.dev 特别提醒全屏动画可能让前庭障碍用户不适。
第六步:处理 SPA、元素和跨文档差异
SPA 用 document.startViewTransition 包住 DOM 更新;元素范围过渡只影响调用元素及其后代;跨文档导航要满足同源,并在两端 CSS 中启用 @view-transition。不要把同文档的 JS 回调假设直接带到跨文档生命周期。
第七步:设计跳过、并发和失败路径
用户快速连续点击时,先取消或合并旧导航,避免多个过渡同时争夺同一节点。通过 skipTransition() 或超时快速结束卡住的动画。业务状态已更新时,动画失败只影响视觉效果,不应触发重复提交、重复请求或错误回滚。
第八步:验证性能和真实用户体验
监控过渡启动到完成的时间、跳过比例、长任务、CLS、输入延迟和低端设备错误。用大列表、慢网络、异步拒绝、快速返回、屏幕阅读器和减少动态效果做演练。确认截图和动画不会让真实交互等待更久。
设计取舍与边界
取舍一:整页过渡还是局部过渡
整页过渡实现简单,但截图和动画成本更高,也更容易干扰焦点和滚动。局部过渡需要稳定命名和更精确的组件边界,适合高频交互与大页面。
取舍二:等待数据还是立即过渡
等待数据可以避免旧内容与新内容错位,但会延长响应时间。优先预取数据,无法预取时给出明确的加载状态;过渡不是替代加载反馈的理由。
取舍三:自定义动画还是浏览器默认
默认动画更安全、更易维护。只有在明确的导航语义和验证预算下才覆盖伪元素动画,并为减少动态效果提供同等功能的静态状态。
失败演练与演进计划
演练一:浏览器不支持或回调拒绝
在不支持 API 的浏览器直接更新,模拟 Promise 拒绝并确认页面状态仍正确。错误只记录可诊断信息,不显示实现细节,也不阻塞用户继续操作。
演练二:快速导航与异步竞态
连续点击两个链接,故意让第一个请求更慢。验证旧过渡不会覆盖新状态,焦点最终落在当前页面,且不会发送重复副作用请求。
演练三:减少动态效果和低端设备
开启系统 reduce motion,在 CPU 受限设备运行大列表切换。确认动画被关闭或缩短,输入延迟和布局稳定性仍在预算内。
常见误区与追问
误区一:没有回退路径
API 不可用时仍必须完成同一 DOM 或路由更新。渐进增强不能把业务状态绑定在动画成功上。
误区二:在回调中无限等待网络
快照已创建后长时间等待会让用户看到冻结的旧页面。提前准备数据,或设置超时并跳过动画。
误区三:给大量元素设置同名视图
重复名称会造成匹配歧义和额外截图。只命名真正需要共享位置的单个元素,并保证名称稳定唯一。
误区四:忽略焦点和滚动
视觉过渡结束不代表键盘用户回到了正确位置。显式恢复焦点、滚动和语义标题。
误区五:把 reduce motion 当成删除功能
用户只要求减少动态效果,不是关闭状态变化。保持同样的信息和交互,只改变动画表现。
误区六:动画失败就回滚业务状态
finished 失败通常只说明视觉阶段失败。不要重复提交或回滚已经成功的状态更新。