题干与适用场景
产品希望在列表、详情和筛选页面之间提供连贯转场,同时保留原生导航、无障碍和低端设备性能。请设计一个基于 View Transition API 的渐进增强方案:同文档更新使用 document.startViewTransition(),跨文档导航使用 CSS 声明,并说明在 API 不可用、DOM 更新失败或用户开启减少动效时的行为。
这道题适合前端、设计系统和性能工程岗位。MDN、Chrome for Developers 与 CSS View Transitions 规范分别定义了 ViewTransition 生命周期、同文档和跨文档机制、伪元素动画树及跳过转场的方法。本文根据公开标准资料整理,不声称是公司真题。
面试官考察点
面试官关注你是否把“动画效果”拆成导航语义、DOM 更新、动画生命周期和回退边界。强回答会提到 ready、updateCallbackDone、finished、skipTransition()、prefers-reduced-motion、唯一 view-transition-name 和同源限制;普通回答只会套一个淡入 CSS。
回答前需要澄清的问题
- 目标是 SPA 内状态切换、跨页面导航,还是两者都支持?
- 哪些元素需要共享身份,列表卡片到详情页是否有稳定 ID?
- 是否要求支持无 JavaScript 的核心导航和搜索引擎抓取?
- 动画时长、低端设备预算和减少动效用户的产品要求是什么?
30 秒回答框架
“我会把原生导航和状态更新作为真源,View Transition 只做渐进增强。同文档用 startViewTransition(update) 包住同步或异步 DOM 更新,等待 ready 后播放伪元素动画;跨文档用 @view-transition { navigation: auto; } 并给共享元素稳定名称。API 不存在、更新失败或用户偏好减少动效时直接完成导航,必要时调用 skipTransition()。通过唯一命名、超时和性能指标避免转场阻塞业务。”
分步骤深入解答
同文档转场先调用 document.startViewTransition(() => update())。浏览器捕获旧状态,执行更新回调,再生成新状态;返回的 ViewTransition 提供 ready、updateCallbackDone 和 finished 等 Promise。更新回调抛错时,页面状态仍需由应用处理,不能把业务提交绑定在动画成功上。动画可通过 ::view-transition-old(root)、::view-transition-new(root) 等伪元素定制。
共享元素使用 view-transition-name 建立旧节点与新节点的配对。名称在同一棵转场树中应唯一;列表渲染必须用稳定业务 ID,不能把数组索引当身份,否则排序或筛选会产生错误的飞行动画。元素过多时只给关键节点命名,避免截图和合成成本增长。
跨文档转场依赖同源导航和 CSS @view-transition 声明,不需要 JavaScript 调用 startViewTransition()。它适合多页应用或完整文档导航,但受浏览器版本、同源策略和页面 CSS 支持影响。导航仍应能在没有转场的情况下完成,不能用动画 Promise 阻塞服务器响应或链接默认行为。
生命周期要有超时与跳过路径。ready 长时间未完成时记录原因并调用 skipTransition();用户点击返回、提交表单或离开页面时优先执行导航。finished 只用于清理临时状态和指标,不应作为保存数据的确认信号。对异步数据更新,先确定状态提交边界,再决定是否等待转场。
无障碍策略包括尊重 @media (prefers-reduced-motion: reduce),在该媒体查询下把动画时长降为零或直接跳过;保持焦点、标题和阅读顺序,不用视觉位置代替语义更新。键盘用户、屏幕阅读器和慢速设备都应获得相同内容结果。
渐进增强通过能力检测和真实导航回退实现。若 document.startViewTransition 不存在,就直接调用更新函数;跨文档不支持时保留普通链接。记录支持率、跳过率、ready 延迟、动画帧率和页面交互延迟,按浏览器与设备分层观察。转场只影响呈现,不改变缓存、权限、表单提交或路由的正确性。
高质量示范回答
我会让导航和状态提交独立于动画。SPA 使用 startViewTransition(update),在 ready 后只动画必要的共享元素;多页应用使用同源的 @view-transition 声明。每个共享元素有稳定且唯一的业务名称,异步更新失败时跳过转场并展示正常错误状态。
浏览器不支持时走原生更新或链接,prefers-reduced-motion 时关闭或压缩动画,生命周期设置超时并保留 skipTransition()。焦点、标题和语义顺序不依赖视觉效果。监控支持率、跳过率和交互延迟,灰度验证后再扩大命名元素范围。
常见错误
- 错误表现 → 用数组索引作为
view-transition-name;失败原因 → 排序后旧新节点错误配对;修正方法 → 使用稳定业务 ID 且保证名称唯一。 - 错误表现 → 等待
finished才提交表单;失败原因 → 动画失败会阻塞业务动作;修正方法 → 先提交状态,动画只负责呈现。 - 错误表现 → 只写 SPA 方案;失败原因 → 多页导航没有 API 调用入口;修正方法 → 用
@view-transition覆盖同源跨文档导航,并保留普通链接。 - 错误表现 → 忽略减少动效偏好;失败原因 → 给敏感用户造成眩晕或认知负担;修正方法 → 媒体查询下缩短或跳过动画。
追问及应对
更新回调抛错时,转场和业务状态如何处理?
把更新回调视为业务状态变更边界,先保证异常可捕获、状态可回滚或展示错误;转场 Promise 只用于动画生命周期。若 updateCallbackDone reject,调用 skipTransition() 或让浏览器结束默认呈现,不能把错误吞掉。
为什么不能给所有元素都设置共享名称?
每个名称需要在旧树和新树中形成明确配对,数量过多会增加快照、布局和合成成本,也可能制造错误匹配。只标记用户能理解的关键对象,其余内容使用根节点淡入或不动画。
如何验证转场没有损害性能?
分浏览器和设备记录 ready 延迟、动画帧率、长任务、首次输入延迟与跳过率,并用真实列表规模压测。低端设备或帧率持续下降时自动缩小动画范围或跳过,确保核心导航耗时不变。