跳转到内容

Navigation API

发布于 更新于

一句话: 根据 MDN 的说明,“Navigation API 提供了发起、拦截和管理浏览器导航操作的 能力”——它是 History API 与 window.location 的继任者,专门面向单页应用(SPA)的需求。

该 API 通过 Window.navigation 访问,根据 MDN 的说明,它“返回一个全局 Navigation 对象的引用”——每个 window 都有自己独立的实例。其中最重要的事件是 navigate,根据 MDN 的 Navigation API 概览页面,该事件“在任意类型的导航发起时触发,这意味着你可以从 一个中心位置控制所有页面导航,非常适合 SPA 框架中的路由功能”。MDN 的 navigate_event 参考文档对同一事件的描述更为具体:“在任意类型的导航发起时触发,使你能够按需拦截”。

根据 MDN 的 browser-compat-data,Navigation 接口自 Chrome 102 起受支持(Chrome for Android 同样自 102 起),自 Firefox 147 起受支持,自 Safari 26.2 起受支持(Safari on iOS 同样自 26.2 起)——兼容性数据中 Edge、Opera、Samsung Internet 的条目标记为 "mirror",意味着它们的支持数据是根据各自对应的上游 Chromium 版本推算得出的,而不是 直接给出的数值;这并不代表这些浏览器与 Chrome 使用相同的版本号。 NavigateEvent 接口本身与之同期上线,同样自 Chrome 102 起;只有其当前名称的 intercept() 方法稍晚才出现,即 Chrome 105(此前存在一个名称不同的方法 transitionWhile,自 Chrome 102 起存在,直到 Chrome 108 被移除)。Firefox 147 与 Safari 26.2 则是将 NavigateEvent 与 intercept() 一并上线。

监听 navigate 事件并调用 intercept(),用自定义逻辑处理导航而不是整页加载。以下示例 假设页面的应用外壳(app shell)已经渲染了一个 <div id="article"></div> 容器供其更新:

navigation.addEventListener("navigate", (event) => {
// 有些导航(例如跨源导航)无法被拦截——让浏览器按常规方式处理。
if (!event.canIntercept) {
return;
}
// 不拦截 hash 导航或下载。
if (event.hashChange || event.downloadRequest !== null) {
return;
}
const url = new URL(event.destination.url);
event.intercept({
async handler() {
renderArticlePagePlaceholder();
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
});
function renderArticlePagePlaceholder() {
document.querySelector("#article").innerHTML = "<p>加载中…</p>";
}
async function getArticleContent(pathname) {
const response = await fetch(`/api/articles${pathname}`);
return response.text();
}
function renderArticlePage(html) {
document.querySelector("#article").innerHTML = html;
}

根据 MDN 的 navigate_event 参考文档,调用 handler() 时 URL 已经更新,因此其自身 示例会先渲染占位内容,再在获取到真实内容后替换——上面展示的正是这一模式。

在注册处理函数之前,需要同时检测 window 上的 navigation 与 NavigateEvent.prototype.intercept——根据上文的兼容性数据,window.navigation 从 Chrome 102 就存在,但 intercept() 直到 Chrome 105 才上线,因此只检测 window 会让 Chrome 102–104 误判为支持,并在首次导航时抛出 TypeError。不支持时应回退为真正可用的 链接跳转与 History API 导航。以下示例假设页面已存在 <div id="article"></div> 容器, 以及若干站内链接(例如 <a href="/articles/1">):

function supportsNavigationApi() {
return (
"navigation" in window &&
typeof NavigateEvent !== "undefined" &&
typeof NavigateEvent.prototype.intercept === "function"
);
}
if (supportsNavigationApi()) {
navigation.addEventListener("navigate", handleAppNavigation);
} else {
// 此处没有可用的 Navigation API——依赖常规的链接点击以及
// History API 的 pushState/popstate 来实现路由。
setupHistoryApiRouting();
}
function handleAppNavigation(event) {
if (
!event.canIntercept ||
event.hashChange ||
event.downloadRequest !== null ||
typeof event.intercept !== "function"
) {
return;
}
const url = new URL(event.destination.url);
event.intercept({
async handler() {
const response = await fetch(`/api/articles${url.pathname}`);
document.querySelector("#article").innerHTML = await response.text();
},
});
}
function setupHistoryApiRouting() {
// 拦截同源链接点击,改为推入新的 History 记录,而不是触发整页加载。
document.addEventListener("click", (event) => {
const link = event.target.closest("a[href]");
if (
!link ||
link.origin !== location.origin ||
event.defaultPrevented ||
event.button !== 0 ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey
) {
return;
}
event.preventDefault();
history.pushState(null, "", link.href);
renderArticleFromPath(location.pathname);
});
// 前进/后退导航时,渲染与地址栏匹配的文章。
window.addEventListener("popstate", () => {
renderArticleFromPath(location.pathname);
});
renderArticleFromPath(location.pathname);
}
async function renderArticleFromPath(pathname) {
const response = await fetch(`/api/articles${pathname}`);
document.querySelector("#article").innerHTML = await response.text();
}
  • 根据 MDN 的 navigate_event 参考文档,应“在不该被拦截的导航上提前退出”——例如 event.hashChange 或 event.downloadRequest 的情况——并非所有 navigate 事件都 应该被拦截。
  • Chrome 的 intercept() 方法取代了此前一个已被移除的方法 transitionWhile——针对 Chrome 102–107 编写的、使用 transitionWhile 的示例代码在当前浏览器上无法运行。
  • 根据 MDN 的兼容性数据,NavigateEvent/intercept() 分别在 Firefox 147 与 Safari 26.2 才落地,明显晚于 Chrome——站点仍需要为低于这些版本的浏览器保留 History API 回退路径。
  • 根据 MDN 的说明,navigate 事件是所有导航类型的中心事件,但并非所有导航都能被 拦截:根据 MDN 的 NavigateEvent.canIntercept 参考文档,跨源导航就是 canIntercept 为 false 的情况之一,因此处理函数必须检查它,并让浏览器按常规方式处理这类导航, 而不是尝试拦截它们。