navigation api 的 intercept() 仅阻断跳转,不提供动画;startviewtransition() 必须在 dom 同步更新时调用,若在 intercept 回调中异步执行(如 await fetch 后调用),快照将捕获旧状态导致动画失效或报错 invalidstateerror。

Navigation API 的 intercept() 本身不提供过渡动画能力,它只负责阻断默认跳转;自定义过渡必须由你手动控制 DOM 更新 + 触发 View Transition,且两者必须严格配合,否则动画会失效或卡顿。
为什么直接在 intercept 回调里 startViewTransition 会失败
常见错误现象:event.intercept() 后调用 document.startViewTransition(),但页面“闪一下”就完成,没有过渡动画;或者报错 InvalidStateError: No view transition is active。
根本原因:View Transition 快照是在 startViewTransition 调用**瞬间**拍的,而 intercept() 回调里若包含异步操作(比如 await fetch()、router.resolve()),DOM 更新会延后,快照捕获的是旧状态甚至空容器。
- 浏览器不会等你 fetch 完再拍快照——它只认回调是否同步执行
-
intercept()回调必须立即返回,不能await,否则导航会被挂起,用户点击无响应 - View Transition 要求“旧元素还在、新元素已挂载、二者有匹配的
view-transition-name”,缺一不可
正确链路:fetch → 挂载新 DOM → startViewTransition → updateHistory
必须把数据获取和 DOM 渲染拆开,确保 startViewTransition 的回调内只做**同步、可见的 DOM 替换**。
示例逻辑(简化版):
navigator.navigation.addEventListener('navigate', async (event) => {
if (!event.canIntercept) return;
event.intercept(async ({ signal }) => {
// ✅ 第一步:先取数据(在 intercept 外或顶部 await)
const res = await fetch(event.destination.url, { signal });
const html = await res.text();
// ✅ 第二步:提前创建并挂载新容器(确保它在 document 中)
const newArticle = document.createElement('article');
newArticle.innerHTML = html;
document.body.appendChild(newArticle); // 必须 append,不能只 createElement
// ✅ 第三步:同步执行 transition —— 此时新旧元素都存在且可匹配
document.startViewTransition(() => {
// 移除旧内容,插入新内容(同步!)
document.querySelector('main').replaceWith(newArticle);
});
// ✅ 第四步:手动更新 history(intercept 不自动 push)
history.pushState({}, '', event.destination.url);
});
});
- 所有
fetch和解析必须在startViewTransition回调外完成 -
newArticle必须已插入 DOM(哪怕暂时visibility: hidden),否则快照捕获不到 -
replaceWith是关键:它保证旧节点未销毁前,新节点已存在,view-transition-name才能配对
view-transition-name 配对失败的三个高发场景
90% 的 View Transition 不生效,问题不在 JS,而在 HTML/CSS 层面的 view-transition-name。
- 旧
<h1 view-transition-name="title"></h1>和新<h1 view-transition-name="Title"></h1>大小写不一致 → 不匹配 - 新内容里用了
view-transition-name: title-${id},但id值变了 → 名称不恒定 - 旧元素被
display: none或opacity: 0隐藏,且样式在快照前已生效 → 浏览器拍到的是“不可见”,快照为空
验证方法:打开 Chrome DevTools → Layers 面板 → 看 transition 元素是否生成了独立合成层;若没有,加 will-change: transform 强制提升。
兼容性兜底必须手动监听 a 标签
navigator.navigation 在 Firefox、Safari 和多数 WebView 中不可用,仅 Chrome/Edge 102+ 支持。检测失败后,不能只退回到 history.pushState,因为 popstate 拿不到目标 URL 的结构化信息,也无法拦截 <a></a> 点击。
兜底方案要补两件事:
- 给所有内部链接加
data-nav="true",并监听document.addEventListener('click', e => { if (e.target.matches('a[data-nav]')) { ... } }) - 用
new URL(e.target.href)手动解析目标地址,模拟event.destination.url行为 - View Transition 兜底只能放弃,改用 CSS
transition+opacity/transform做简易淡入
真正难的不是写 intercept,而是让整个导航链路——从点击、拦截、数据加载、DOM 替换、动画触发、history 更新——全部在毫秒级时间窗口内完成且不破坏语义。稍有延迟,用户就会感知为“卡住”或“闪退”。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











