用户判断一个网站"快不快",很大程度上取决于切换瞬间发生了什么。白屏 300ms 会让人觉得慢,哪怕实际加载只用了 200ms。
View Transitions API 解决的正是这个感知问题。
# 最小可用示例
async function navigate(url) {
if (!document.startViewTransition) {
location.href = url;
return;
}
const res = await fetch(url);
const html = await res.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
document.startViewTransition(() => {
document.body.replaceWith(doc.body);
});
}
浏览器会自动做三件事:截取旧状态快照、执行你的 DOM 变更、截取新状态快照,然后在两张快照之间做交叉淡入。
# 给元素命名,才有共享元素动画
默认是整页淡入淡出。想让标题"飞"过去,需要 view-transition-name:
.post-title { view-transition-name: post-title; }
.post-cover { view-transition-name: post-cover; }
两个页面上同名,浏览器就会把它们当成同一个元素做位移动画。同一时刻页面上不能有两个同名元素,否则转场直接失效——这是最常见的翻车点。
# 三个容易翻车的细节
# 1. 滚动位置
转场期间页面被截图,滚动会被重置。记得恢复:
document.startViewTransition(() => {
document.body.replaceWith(doc.body);
window.scrollTo(0, 0);
});
# 2. 动画时长与可达性
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 320ms;
}
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
前庭功能敏感的用户会因位移动画不适,这段媒体查询不是可选项。
# 3. 别让转场挡住交互
转场期间页面是"活的快照",用户点击无效。如果 fetch 很慢,先请求再启动转场,不要先启动再等待:
// ✅ 数据就绪后再开始,转场只覆盖 DOM 替换那一瞬
const html = await fetchHtml(url);
document.startViewTransition(() => swap(html));
# MPA 版本:一行 CSS
如果站点是多页的,Chromium 已经支持跨文档转场:
@view-transition {
navigation: auto;
}
加到两个页面的 CSS 里,站内跳转自动获得淡入效果。不支持的环境什么都不会发生,天然渐进增强。
体验优化最划算的地方,往往不是让事情变快,而是让等待变得可以被理解。