发布于 2026-01-06 1 阅读
0

使用原生 JavaScript 进行虚拟 URL 导航

使用原生 JavaScript 进行虚拟 URL 导航

我在一个 GitHub 项目中遇到了一个问题,维护者希望动态地更改页面内容,使其感觉像是一个“原生”页面,而无需实际创建另一个 HTML 文件。

他还希望他的网站只使用原生 JS 和 HTML 文件,因此向他介绍 Angular、Svelte 或任何其他框架都不现实。

幸运的是,我发现 HTML5 中引入了一个有趣的 API,可以完美地解决这类问题。

让我们一步一步地来探讨这个问题。

设置

为了举例说明,我们将构建一个非常简单的页面,以模拟使用某种路由功能。

首先,我们将创建一个简单的HTML页面:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Fake URL navigation</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <h1>Fake routing</h1>
    <button id="details">Show details</button>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

目前来看,这并没有造成太大影响。

更改网址

我们现在希望每当用户点击按钮时都更改 URL。

为此,我们可以使用pushState函数以编程方式向历史记录添加新条目,这也会导致 URL 被修改。

它的使用方法相当简单,由三个参数组成:

  1. 将通过 URL 推送的状态,可以是轻量级的 JS 对象,也可以是某种结构化数据。
  2. 由于向后兼容性,这是一个未使用的必填参数,我们可以将其替换为空字符串。
  3. 我们想要推送的实际 URL

我们来添加一小段脚本来修改它:

<script>
  document.getElementById("details").onclick = (_event) => history.pushState({}, "", "/details");
</script>
Enter fullscreen mode Exit fullscreen mode

然后测试一下:

URL更新

太好了!我们现在可以专注于内容了。

更新页面

为了更新我们的内容,我们需要某种模板,并将当前的 HTML 正文替换为该模板。

使用原生 API,这非常简单:

<script>
+ const template = "<h1>Details</h1> <p>Some highly useful information</p>";

  document.getElementById("details").onclick = (_event) => {
    history.pushState({}, "", "/details");
+   document.body.innerHTML = template;
  };
</script>
Enter fullscreen mode Exit fullscreen mode

我们现在有了自己的网页!

替换内容

修复返回按钮

按照我们目前的解决方案,一旦显示详情页,就无法在不重新加载整个页面的情况下再次显示索引页。

为了解决这个问题,我们可以先创建一个本地历史记录,方法是在删除 body 元素之前,将其内容推入堆栈:

<script>
  const template = "<h1>Details</h1> <p>Some highly useful information</p>";

+ const bodyHistory = [];

  document.getElementById("details").onclick = (_event) => {
    history.pushState({}, "", "/details");

+   bodyHistory.push(document.body.innerHTML);
    document.body.innerHTML = template;
  };
</script>
Enter fullscreen mode Exit fullscreen mode

然后,我们可以使用与以下事件互为镜像的事件pushStatepopstate

通过监听,我们就能知道何时按下了后退按钮,从而能够恢复页面之前的显示内容:

<script>
  const template = "<h1>Details</h1> <p>Some highly useful information</p>";

  const bodyHistory = [];

  document.getElementById("details").onclick = (_event) => {
    history.pushState({}, "", "/details");

    bodyHistory.push(document.body.innerHTML);
    document.body.innerHTML = template;
  };

+ onpopstate = (_event) => {
+   const previousContent = bodyHistory.pop();
+
+   if (previousContent) {
+     document.body.innerHTML = previousContent;
+   }
+ };
</script>
Enter fullscreen mode Exit fullscreen mode

此时,虽然可以回溯历史记录,但我们将无法再次导航,因为我们恢复了整个页面,但事件监听器尚未重新设置。

onpopstate一个快速的解决方法是,按照我们最初添加此逻辑时的方式,在我们的事件处理程序中重新注册它。

让我们把这个逻辑提取到一个专门的函数中,并称之为:

<script>
  const template = "<h1>Details</h1> <p>Some highly useful information</p>";

  const bodyHistory = [];

+ registerHandler();

+ function registerHandler() {
+   document.getElementById("details").onclick = (_event) => {
+     history.pushState({}, "", "/details");
+
+     bodyHistory.push(document.body.innerHTML);
+     document.body.innerHTML = template;
+   };
+ };

  onpopstate = (_event) => {
    const previousContent = bodyHistory.pop();

    if (previousContent) {
      document.body.innerHTML = previousContent;
+     registerHandler();
    }
  };
</script>
Enter fullscreen mode Exit fullscreen mode

我们完成了!

背部


在这篇短文中,我们看到了如何利用这对组合pushState/popState给用户一种浏览不同页面的感觉。

这是对某种虚拟导航的简单介绍,但就我而言,它帮助我成功地将其应用到这位维护者的项目中。

但是请注意,当用户访问的 URL 与实际路由不匹配时,重新加载页面将导致 HTTP 404 错误。

...#details一种解决方案是用哈希值(例如 `<h1> ` 而不是 `<h2> `)替换指向虚拟页面的导航.../details。这样,浏览器在重新加载时仍然会渲染根页面,而我们可以拦截哈希值后面的内容,并将其用于渲染详情页面。

希望你在那里学到了有用的东西!


演示中使用的所有源代码:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Fake URL navigation</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    <h1>Fake routing</h1>
    <button id="details">Show details</button>
  </body>

  <script>
    const template = "<h1>Details</h1> <p>Some highly useful information</p>";

    const bodyHistory = [];

    registerHandler();

    function registerHandler() {
      document.getElementById("details").onclick = (_event) => {
        history.pushState({}, "", "/details");

        bodyHistory.push(document.body.innerHTML);
        document.body.innerHTML = template;
      };
    }

    onpopstate = (_event) => {
      const previousContent = bodyHistory.pop();

      if (previousContent) {
        document.body.innerHTML = previousContent;
        registerHandler();
      }
    };
  </script>
</html>
Enter fullscreen mode Exit fullscreen mode
文章来源:https://dev.to/pbouillon/virtual-url-navigation-using-vanilla-javascript-4o38