JavaScript / 浏览器 API / CSS
View Transitions API 实战:给单页应用加上可控的页面过渡
单页应用切换列表和详情时,DOM 往往在一瞬间全部替换。功能没有问题,但用户很难理解“刚才点击的卡片去了哪里”。过去要做共享元素过渡,需要克隆节点、读取位置、计算 transform,还要处理滚动与窗口变化,维护成本很高。
View Transitions API 把页面更新前后的视觉快照和动画阶段交给浏览器管理。开发者只需说明“什么时候更新 DOM”以及“哪些元素应该建立视觉对应关系”,就能得到渐进增强的过渡效果。
一、它解决的是视图连续性,不是路由本身
View Transitions API 不负责数据请求,也不是路由库。它包裹一次已有的 DOM 更新:浏览器先捕获旧状态,执行更新回调,再捕获新状态,最后在两组快照之间创建动画。
最小示例如下:
1 | function renderView(view) { |
能力检测非常重要。不支持 API 的浏览器仍然执行同一份 DOM 更新,只是没有动画。过渡属于增强层,不应该成为导航成功的前提。
二、浏览器在调用期间做了什么
一次同文档过渡可以理解为四步:
- 捕获当前页面的旧快照。
- 调用传给
startViewTransition()的更新回调。 - 等待回调返回的 Promise 完成,然后捕获新快照。
- 使用专用伪元素在旧快照和新快照之间动画。
因此异步渲染可以写成:
1 | async function openProduct(productId) { |
不要把慢网络请求全部放进更新回调。旧快照已经捕获后,长时间等待会让页面停在过渡中间。通常先拿到页面必须的数据,再启动过渡;次要内容可以在新视图出现后继续加载。
返回的 ViewTransition 对象提供 ready、updateCallbackDone 和 finished 等 Promise,分别适合在动画准备好、DOM 更新结束和整个过渡结束时安排后续工作。业务流程不应为了动画而依赖脆弱的固定时长。
三、从根视图淡入淡出开始
如果不指定共享元素,浏览器会对页面根视图建立过渡。可以通过专用伪元素调整动画:
1 | ::view-transition-old(root) { |
根视图动画适合简单内容切换。时长要短,避免每次点击都让用户等待一场演出。信息工具、后台系统和高频操作通常比营销页更需要克制。
四、用 view-transition-name 建立共享元素
列表中的商品图片和详情页大图可以使用相同的过渡名称:
1 | .product-card[data-active="true"] .product-card__image, |
1 | ::view-transition-group(product-hero) { |
浏览器会匹配更新前后的 product-hero,并处理尺寸与位置之间的变化。名称在同一时刻必须唯一;如果列表中每张卡片都设置成同一个名字,过渡会失败或表现异常。
可以只给被点击的卡片添加状态:
1 | function selectCard(card) { |
动态名称也可以基于稳定 ID 生成,但不要把未经处理的任意文本直接拼进 CSS 标识符。名称的职责是建立视觉身份,应该稳定、唯一且可预测。
五、与路由和历史记录配合
单页应用仍要正确更新 URL、标题和历史记录:
1 | async function goToProduct(productId) { |
动画不能替代可访问的导航语义。视图变化后仍要维护标题、焦点位置、返回键行为和读屏提示。浏览器视觉上连续,不代表辅助技术已经知道页面发生了变化。
popstate 返回时也可以使用过渡,但方向可能与前进导航不同。可以在根元素加 data-navigation="back",为前进和返回使用不同动画,而不是假设所有切换都从右向左。
六、尊重减少动态效果偏好
部分用户会因为大幅缩放和移动感到不适。必须为 prefers-reduced-motion 提供弱化或取消方案:
1 | @media (prefers-reduced-motion: reduce) { |
也可以在 JavaScript 中跳过过渡:
1 | const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)'); |
七、性能和交互陷阱
View Transitions API 不是性能优化器。更新回调里如果执行 300ms 的同步计算,主线程仍会卡住。大面积滤镜、超大共享元素和同时运行太多命名过渡,也会增加合成压力。
还要检查这些边界:
- 用户快速连续导航时,新的过渡如何处理旧过渡?
- 动画期间按钮是否仍能重复触发请求?
- 共享元素在新旧视图中是否都存在?
- 滚动位置变化是否符合预期?
- 固定定位导航是否应该参与根视图快照?
- 更新回调抛错时,页面是否仍能呈现可用状态?
动画只能解释状态变化,不能掩盖状态错误。先保证无动画导航完全正确,再加过渡。
八、推荐落地顺序
- 为现有渲染函数添加能力检测和根视图淡入淡出。
- 检查前进、返回、刷新和深链接是否仍然正确。
- 选择一个最有价值的共享元素,不要一次给所有节点命名。
- 加入减少动态效果策略。
- 在低端设备录制性能,确认没有长任务和明显掉帧。
- 用键盘与读屏流程检查焦点、标题和状态通知。
好的页面过渡应该帮助用户理解空间关系,而不是提醒用户“这里用了动画 API”。从短暂的根视图过渡开始,逐步加入少量共享元素,并把无动画路径始终保持为完整功能,才能让体验提升而不是增加新的依赖。
参考资料
- MDN:View Transition API — https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API
- MDN:Document.startViewTransition() — https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition