PJAX和html5 history api
什么是PJAX
PJAX其实是一组技术的简称,即HTML5的pushState+ajax,实现的效果是无刷新的改变页面某个部分的内容(ajax),同时地址栏URL又能随之改变(HTML5 push State)
访问github的时候当查看某个文件的源码的时候发现其实整个页面没有刷新而URL改变了其实也是用到了PJAX,还有如网易云音乐等
为什么会产生PJAX
- 为了更好的用户体验,URL能改变同时页面又能局部刷新,即所谓的单页面应用single page application
- 为了能让ajax被搜索引擎抓取,有利于SEO
如何实现PJAX
其实就是利用html5的history api和普通的ajax实现了
history api
Window.history是一个只读属性,用来获取History 对象的引用,History 对象提供了操作浏览器会话历史(浏览器地址栏中访问的页面,以及当前页面中通过框架加载的页面)的接口
history.forward() //等同于浏览器的前进按钮
history.back(); // 等同于点击浏览器的回退按钮
history.go(-1); //移动到指定的历史记录点,-1等同于history.back();1等同于history.forward();类似的,传递参数“2”,你就可以向前移动2页
HTML5引进了history.pushState()方法和history.replaceState()方法,它们允许你逐条地添加和修改历史记录条目。这些方法可以协同window.onpopstate事件一起工作
使用 history.pushState() 会改变 referrer 的值,而在你调用方法后创建的 XMLHttpRequest 对象会在 HTTP 请求头中使用这个值。referrer的值则是创建 XMLHttpRequest 对象时所处的窗口的URL
假设在http://shaynegui.com/foo.html 页面执行以下代码
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");
这将让浏览器的地址栏显示http://shaynegui.com/bar.html 但不会加载bar.html页面也不会检查bar.html是否存在
假设现在用户导航到了http://google.com 然后点击了后退按钮,此时,地址栏将会显示http://shaynegui.com/bar.html 并且页面会触发popstate事件,该事件中的状态对象(state object)包含stateObj的一个拷贝。该页面看起来像foo.html,尽管页面内容可能在popstate事件中被修改。
如果我们再次点击后退按钮,URL将变回http://shaynegui.com/foo.html ,文档将触发另一个popstate事件,这次的状态对象为null。回退同样不会改变文档内容
pushState()方法
pushState()有三个参数:一个状态对象、一个标题(现在会被忽略),一个可选的URL地址
一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate事件都会被触发,并且事件对象的state属性都包含历史记录条目的状态对象的拷贝。任何可序列化的对象都可以被当做状态对象。
标题(title) — FireFox浏览器目前会忽略该参数,虽然以后可能会用上。考虑到未来可能会对该方法进行修改,传一个空字符串会比较安全。或者,你也可以传入一个简短的标题,标明将要进入的状态。
地址(URL) — 新的历史记录条目的地址。浏览器不会在调用pushState()方法后加载该地址,但之后,可能会试图加载,例如用户重启浏览器。新的URL不一定是绝对路径;如果是相对路径,它将以当前URL为基准;传入的URL与当前URL应该是同源的,否则,pushState()会抛出异常。该参数是可选的;不指定的话则为文档当前URL。
某种意义上,调用pushState()有点类似于设置window.location='#foo',它们都会在当前文档内创建和激活新的历史记录条目。但pushState()有自己的优势
- 新的URL可以是任意的同源URL,与此相反,使用window.location方法时,只有仅修改 hash 才能保证停留在相同的document中
- 根据个人需要来决定是否修改URL。相反,设置window.location='#foo',只有在当前hash值不是foo时才创建一条新历史记录
- 你可以在新的历史记录条目中添加抽象数据。如果使用基于hash的方法,你只能把相关数据转码成一个很短的字符串
注意pushState()方法永远不会触发hashchange事件,即便新的地址只变更了hash,后面会说道hashchange
replaceState()方法
history.replaceState()操作类似于history.pushState(),不同之处在于replaceState()方法会修改当前历史记录条目而并非创建新的条目;当你为了响应用户的某些操作,而要更新当前历史记录条目的状态对象或URL时,使用replaceState()方法会特别合适
popstate事件
每当处于激活状态的历史记录条目发生变化时,popstate事件就会在对应window对象上触发. 如果当前处于激活状态的历史记录条目是由history.pushState()方法创建,或者由history.replaceState()方法修改过的, 则popstate事件对象的state属性包含了这个历史记录条目的state对象的一个拷贝拷贝
调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在其他浏览器操作时触发, 比如点击后退按钮(或者在JavaScript中调用history.back()方法).
当网页加载时,各浏览器对popstate事件是否触发有不同的表现,Chrome 和 Safari会触发popstate事件, 而Firefox不会
假如当前网页地址为http://example.com/example.html, 则运行下述代码后
window.onpopstate = function(event) {
alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
};
//绑定事件处理函数.
history.pushState({page: 1}, "title 1", "?page=1"); //添加并激活一个历史记录条目 http://example.com/example.html?page=1,条目索引为1
history.pushState({page: 2}, "title 2", "?page=2"); //添加并激活一个历史记录条目 http://example.com/example.html?page=2,条目索引为2
history.replaceState({page: 3}, "title 3", "?page=3"); //修改当前激活的历史记录条目 http://ex..?page=2 变为 http://ex..?page=3,条目索引为2
history.back(); // 弹出 "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // 弹出 "location: http://example.com/example.html, state: null
history.go(2); // 弹出 "location: http://example.com/example.html?page=3, state: {"page":3}
即便进入了那些非pushState和replaceState方法作用过的(比如http://example.com/example.html) 没有state对象关联的那些网页, popstate事件也仍然会被触发
读取当前状态
在页面加载时,可能会包含一个非空的状态对象。这种情况是会发生的,例如,如果页面中使用pushState()或replaceState()方法设置了一个状态对象,然后用户重启了浏览器。当页面重新加载时,页面会触发onload事件,但不会触发popstate事件。但是,如果你读取 history.state 属性,你会得到一个与 popstate 事件触发时得到的一样的状态对象
你可以直接读取当前历史记录条目的状态,而不需要等待popstate事件
var currentState = history.state
兼容性列表如下
Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
replaceState, pushState | 5 | 4.0 (2.0) | 10 | 11.50 | 5.0 |
history.state | 18 | 4.0 (2.0) | 10 | 11.50 | 6.0 |
对于不支持HTML5 history api如何实现呢
可以优雅降级为url中hash的内容来实现(即把需要获取的新页面的URL当作参数写到hash后面),如:http://www.shaynegui.com/#/foo.html 然后监听hashchange事件
hashchange事件会在页面URL中的片段标识符(第一个#号开始到末尾的所有字符,包括#号)发生改变时触发.
hashchange 事件对象有下面两个属性
- newURL 当前页面新的URL
- oldURL 当前页面旧的URL
window.onhashchange = function(event){
alert(event.newURL);
alert(event.oldURL);
}
此事件的兼容性列表如下
Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
Basic support | 5.0 | 3.6 (1.9.2) | 8.0 | 10.6 | 5.0 |
那么对于ie8一下的版本,因为无法监听hashchange事件只能通过定时器轮询来知道hash是否改变
(function(window) {
// 如果浏览器原生支持该事件,则退出
if ( "onhashchange" in window.document.body ) { return; }
var location = window.location,
oldURL = location.href,
oldHash = location.hash;
// 每隔100ms检测一下location.hash是否发生变化
setInterval(function() {
var newURL = location.href,
newHash = location.hash;
// 如果hash发生了变化,且绑定了处理函数...
if ( newHash != oldHash && typeof window.onhashchange === "function" ) {
// execute the handler
window.onhashchange({
type: "hashchange",
oldURL: oldURL,
newURL: newURL
});
oldURL = newURL;
oldHash = newHash;
}
}, 100);
})(window);
因此对于不支持history api和hashchange事件的IE8以下的浏览器,可以使用轮询url的hash部分实现一样的PJAX效果