使用 HTML5 历史记录 API

Avatar of Robin Rendle
Robin Rendle 发布

DigitalOcean 为您的旅程每个阶段提供云产品。 立即开始使用 价值 200 美元的免费信用额度!

HTML5 历史记录 API 使开发人员能够在不完全刷新页面的情况下修改网站的 URL。 这对于使用 JavaScript 加载页面的一部分特别有用,因为内容差异很大,需要新的 URL。

以下是一个示例。 假设一个人从网站的主页导航到帮助页面。 我们使用 Ajax 加载帮助页面的内容。 然后,该用户转到产品页面,我们再次使用 Ajax 加载并替换内容。 然后他们想分享 URL。 使用历史记录 API,我们可以随着用户导航更改页面的 URL,因此他们看到的 URL(以及他们分享或保存的 URL)是相关且正确的。

基础知识

要查看此 API 的功能,只需进入开发者工具并在控制台中键入history。 如果您选择的浏览器支持该 API,那么我们会发现此对象上附加了一系列方法

这些方法可供我们操作浏览器的历史记录。

在本教程中,我们感兴趣的是pushStatereplaceState方法。 返回到控制台,我们可以对这些方法进行一些实验,看看使用它们时 URL 会发生什么。 我们将在后面介绍此函数中的其他参数,但现在我们只需要使用最后一个参数

history.replaceState(null, null, 'hello');

上面的replaceState方法将地址栏中的 URL 替换为'/hello',尽管没有请求资产,并且窗口仍然停留在同一页面上。 然而,这里有一个问题。 点击后退按钮后,我们会发现我们没有返回到本文的 URL,而是回到了我们之前所在的页面。 这是因为replaceState不会操作浏览器的历史记录,它只是替换地址栏中的当前 URL。

要解决此问题,我们需要使用pushState方法

history.pushState(null, null, 'hello');

现在,如果我们点击后退按钮,我们应该发现它按预期工作,因为pushState已经更改了我们的历史记录,包括我们刚刚传入的 URL。 这很有趣,但是如果我们尝试一些不道德的事情,并假装当前的 URL 根本不是 css-tricks.com,而是完全不同的网站呢?

history.pushState(null, null, 'https://twitter.com/hello');

这将引发异常,因为 URL 必须与当前 URL 的相同,否则我们可能会冒重大安全漏洞的风险,并使开发人员能够欺骗人们,让他们相信他们是在完全不同的网站上。

回到传递到此方法中的其他参数,我们可以这样总结它们

history.pushState([data], [title], [url]);
  1. 第一个参数是我们需要的数据,如果网页的状态发生更改,例如,每当有人按下浏览器中的后退或前进按钮时。 请注意,在 Firefox 中,此数据限制为 640k 个字符。
  2. title是第二个参数,它可以是字符串,但在撰写本文时,所有浏览器都忽略它。
  3. 最后一个参数是我们希望出现在地址栏中的 URL。

简短的历史

这些历史记录 API 最重要的事情是它们不会重新加载页面。 过去,更改 URL 的唯一方法是更改window.location,这总是会重新加载页面。 不过,如果唯一更改的是hash(例如,点击<a href="#target">link</a>不会重新加载页面)。

这导致了旧的 hashbang 方法,该方法可以在不完全刷新页面的情况下更改 URL。 众所周知,Twitter 过去也曾使用这种方法,并且因此受到了广泛批评(hash 不是“真正的”资源位置)。

Twitter 已经放弃了这种方法,并且是这种 API 的早期支持者之一。 2012 年,该团队描述了他们的新方法。 在这里,他们概述了他们在处理这种规模时遇到的一些问题,同时还详细介绍了各种浏览器如何实现此规范。

使用 pushState 和 Ajax 的示例

让我们构建一个演示!

在我们想象的界面中,我们希望网站用户找到有关捉鬼敢死队中角色的信息。 当他们选择一张图片时,我们需要显示该角色的文字,我们还需要在每张图片上添加一个 current 类,以便清楚地显示谁被选中。 然后,当我们点击后退按钮时,current 类将跳转到之前选择的字符(前进按钮也是如此),当然,我们还需要将下面的内容切换回来。

这是一个我们可以剖析的工作示例

An example project showing how we might use the History API

此示例的标记非常简单:我们有一个.gallery,其中包含一些链接,每个链接中都有一张图片。 然后是我们在下面要更新的文字,我们想用选定的名字来更新它,以及我们想用每个角色各自的 HTML 文件中的数据来替换的空.content div

<div class="gallery">
  <a href="/peter.html">
    <img src="bill.png" alt="Peter" class="peter" data-name="peter">
  </a>
  <a href="/ray.html">
    <img src="ray.png" alt="Ray" class="ray" data-name="ray">
  </a>
  <a href="/egon.html">
    <img src="egon.png" alt="Egon" class="egon" data-name="egon">
  </a>
  <a href="/winston.html">
    <img src="winston.png" alt="Winston" class="winston" data-name="winston">
  </a>
</div>

<p class="selected">Ghostbusters</p>
<p class="highlight"></p>

<div class="content"></div>

如果没有任何 JavaScript,此页面仍然可以正常运行,点击一个链接会跳转到正确的页面,点击后退按钮也会按用户预期的方式工作。 可访问性和优雅降级万岁!

接下来,我们将转到 JavaScript,在那里我们可以开始使用事件传播(如下所示)在.gallery元素中的每个链接上添加一个事件处理程序

var container = document.querySelector('.gallery');

container.addEventListener('click', function(e) {
  if (e.target != e.currentTarget) {
    e.preventDefault();
    // e.target is the image inside the link we just clicked.
  }
  e.stopPropagation();
}, false);

在此if语句中,我们可以将我们选择的图片的data-name属性分配给data变量。 然后,我们将“.html”追加到它,并将其用作我们pushState方法中的第三个参数,即我们想要加载的 URL(尽管在实际示例中,我们可能想要在 Ajax 请求成功之后才更改 URL)

var data = e.target.getAttribute('data-name'),
  url = data + ".html";
  history.pushState(null, null, url);
    
  // here we can fix the current classes
  // and update text with the data variable
  // and make an Ajax request for the .content element
  // finally we can manually update the document’s title

(或者,我们也可以为此获取链接的 href 属性。)

我已经用注释替换了工作代码,这样我们现在可以专注于pushState方法。

因此,在这一点上,点击图片会更新 URL 栏和 Ajax 请求的内容,但点击后退按钮不会将我们发送到我们之前选择的字符。 我们需要做的是在用户点击后退/前进按钮时发出另一个 Ajax 请求,然后我们需要使用pushState再次更新 URL。

我们首先回到顶部并更新pushState方法的状态参数,以便将这些信息存储起来

history.pushState(data, null, url);

这是上面方法中的第一个参数,data。 现在,设置到该变量的任何内容都将可供我们使用popstate事件访问,该事件在用户点击前进或后退按钮时触发。

window.addEventListener('popstate', function(e) {
  // e.state is equal to the data-attribute of the last image we clicked
});

因此,我们可以根据需要使用这些信息,在这种情况下,我们将我们之前选择的捉鬼敢死队角色的名称作为参数传递给 Ajax requestContent函数,该函数使用 jQuery 的load方法

function requestContent(file) {
  $('.content').load(file + ' .content');
}

window.addEventListener('popstate', function(e) {
  var character = e.state;

  if (character == null) {
    removeCurrentClass();
    textWrapper.innerHTML = " ";
    content.innerHTML = " ";
    document.title = defaultTitle;
  } else {
      updateText(character);
      requestContent(character + ".html");
      addCurrentClass(character);
      document.title = "Ghostbuster | " + character;
  }
});

如果用户点击雷的图片,我们的事件监听器将触发,然后将我们图片的 data 属性存储在pushState事件中。 因此,这将加载ray.html文件,如果用户选择另一张图片然后点击后退按钮,则会调用该文件。 *呼*

这给我们留下了什么呢? 嗯,如果我们点击一个角色然后分享我们更新的 URL,那么将加载该 HTML 文件。 这可能是一种不太令人困惑的体验,并且我们将保留 URL 的完整性,同时为用户提供更快的整体浏览体验。

需要承认的是,上面的示例很简单,因为以这种方式使用 jQuery 加载内容非常混乱,我们可能想将更复杂的对象传递到我们的pushState方法中,但它向我们展示了如何立即开始学习如何使用历史记录 API。 我们先学会走路,然后学会跑步。

下一步

如果我们要在大规模上使用这种技术,那么我们应该考虑使用专门为此目的设计的工具。 例如,pjax 是一个 jQuery 插件,它可以加速使用 Ajax 和 pushState 的过程,但它只支持使用历史记录 API 的浏览器。

History JS 另一方面,支持使用旧的 hash 回退 URL 的旧浏览器。

酷炫的 URL

我喜欢思考 URL,并且我经常引用 Kyle Neath 在 URL 设计 上的这篇文章

URL 是通用的。 它们在 Firefox、Chrome、Safari、Internet Explorer、cURL、wget、您的 iPhone、Android 甚至粘贴在便签纸上都有效。 它们是网络的唯一通用语法。 不要把这一点视为理所当然。 您网站的任何普通半技术用户都应该能够根据对 URL 结构的记忆来导航 90% 的应用程序。 为了实现这一点,您的 URL 需要务实。

这意味着,无论我们想要实现哪些黑客技术或性能提升技巧,网络开发人员都应该珍惜 URL,并且借助 HTML5 历史记录 API,我们只需花费一点点精力就可以解决上面示例中的问题。

常见问题

  • 将 Ajax 请求的地址嵌入锚元素的 href 属性中通常是一个好主意。
  • 确保当用户使用鼠标中键或 Command 键点击时,从 Javascript 点击处理程序中返回 true,这样就不会意外覆盖它们。

进一步阅读

浏览器支持

Chrome Safari Firefox Opera IE Android iOS
31+ 7.1+ 34+ 11.50+ 10+ 4.3+ 7.1+