HTML5 历史记录 API 使开发人员能够在不完全刷新页面的情况下修改网站的 URL。 这对于使用 JavaScript 加载页面的一部分特别有用,因为内容差异很大,需要新的 URL。
以下是一个示例。 假设一个人从网站的主页导航到帮助页面。 我们使用 Ajax 加载帮助页面的内容。 然后,该用户转到产品页面,我们再次使用 Ajax 加载并替换内容。 然后他们想分享 URL。 使用历史记录 API,我们可以随着用户导航更改页面的 URL,因此他们看到的 URL(以及他们分享或保存的 URL)是相关且正确的。
基础知识
要查看此 API 的功能,只需进入开发者工具并在控制台中键入history
。 如果您选择的浏览器支持该 API,那么我们会发现此对象上附加了一系列方法

在本教程中,我们感兴趣的是pushState
和replaceState
方法。 返回到控制台,我们可以对这些方法进行一些实验,看看使用它们时 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]);
- 第一个参数是我们需要的数据,如果网页的状态发生更改,例如,每当有人按下浏览器中的后退或前进按钮时。 请注意,在 Firefox 中,此数据限制为 640k 个字符。
title
是第二个参数,它可以是字符串,但在撰写本文时,所有浏览器都忽略它。- 最后一个参数是我们希望出现在地址栏中的 URL。
简短的历史
这些历史记录 API 最重要的事情是它们不会重新加载页面。 过去,更改 URL 的唯一方法是更改window.location
,这总是会重新加载页面。 不过,如果唯一更改的是hash
(例如,点击<a href="#target">link</a>
不会重新加载页面)。
这导致了旧的 hashbang 方法,该方法可以在不完全刷新页面的情况下更改 URL。 众所周知,Twitter 过去也曾使用这种方法,并且因此受到了广泛批评(hash 不是“真正的”资源位置)。
Twitter 已经放弃了这种方法,并且是这种 API 的早期支持者之一。 2012 年,该团队描述了他们的新方法。 在这里,他们概述了他们在处理这种规模时遇到的一些问题,同时还详细介绍了各种浏览器如何实现此规范。
使用 pushState 和 Ajax 的示例
让我们构建一个演示!
在我们想象的界面中,我们希望网站用户找到有关捉鬼敢死队中角色的信息。 当他们选择一张图片时,我们需要显示该角色的文字,我们还需要在每张图片上添加一个 current 类,以便清楚地显示谁被选中。 然后,当我们点击后退按钮时,current 类将跳转到之前选择的字符(前进按钮也是如此),当然,我们还需要将下面的内容切换回来。
这是一个我们可以剖析的工作示例

此示例的标记非常简单:我们有一个.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
,这样就不会意外覆盖它们。
进一步阅读
- Mozilla 关于操作浏览器历史记录 的文档
- 来自 Dive into HTML5 的 Ajax 图库示例
- Twitter 的 pushState 实现
浏览器支持
Chrome | Safari | Firefox | Opera | IE | Android | iOS |
---|---|---|---|---|---|---|
31+ | 7.1+ | 34+ | 11.50+ | 10+ | 4.3+ | 7.1+ |
我非常喜欢这个 API。它开辟了许多创造性的渠道。我们最近在我们的分页中使用了它,因此它看起来下一页加载速度超级快,而且通常没有任何延迟。有很多因素需要考虑。查看此页面以获取有关分析方面的更多信息:http://davidwalsh.name/ajax-analytics。
嘿 Robin,我非常确定 Opera 在你的浏览器支持表中所说的“26+”之前就支持它。
事实上,即使在 v12.17 中,在控制台中输入
history
也会显示pushState
/replaceState
方法存在。抓住要点,Chris。我通过 caniuse 检查了一下,但看起来根据他们的博客,Opera 11.50 是第一个版本。我会更新这篇文章,谢谢!
Opera 11.5 添加了 pushState,但那些旧的 Presto-Operas 现在已经不重要了。
我在我的新项目中使用了它,请访问 http://tldwhois.truelabs.info/dot-tips
我认为在 pushState 和 replaceState 方法中看到“title”参数在浏览器中不受支持有点奇怪。所以,我们可以做这样的事情吗
?(我将该函数命名为 addState,但可以命名为其他任何名称)
我可以指出另一个库,其中包含针对不支持历史记录对象的浏览器的哈希邦回退吗?
效果很好!
https://github.com/devote/HTML5-History-API
顶部有一个小错误,“所以他们看到的 URL(以及他们分享或保存的 URL)是相关且正确的。”
总体而言,文章写得很好,棒极了。
谢谢 Nicky,我已经修复了那个错误。
“将 Ajax 请求的地址嵌入锚元素的 href 属性中通常是一个好主意。”
对于谷歌机器人来说这是必须的,这样他们就可以跟踪链接并索引所有页面,否则这些页面将不会被索引,对吗?
不,谷歌机器人已经支持基本的 JS 一段时间了。
不,谷歌机器人已经支持基本的 JS 一段时间了。
你在 Twitter pushState 实现链接中缺少一个字符。
应该是 https://blog.twitter.com/2012/implementing-pushstate-for-twittercom
好文章
谢谢!我已经修复了链接。
如果内容中有任何 Hashtags,它会使用 character == null 状态触发 window.addEventLister 函数。有没有办法避免这种情况?