HTML5 history API,创造更好的浏览体验

从Ajax翻页的问题提及

请想象你正在看一个视频下面的评论,在翻到十几页的时候,你发现一个写得稍长,但很是有趣的评论。正当你想要停下滚轮细看的时候,手残按到了F5。而后,页面刷新了,评论又回到了第一页,因此你又要从新翻一次。前端

再或者,你想把这个评论发给别人分享,一面给了别人页面地址(为何不直接复制呢?由于要连带视频等场景啊),一面又要加一句嘱咐:请翻到下面评论的第XX页的XX楼。后端

这就是问题。试想一下,若是浏览器能记住你当前的状态(好比看到了第十几页),而不是一刷新就还原,是否是就显得智能多了?api

为何用Ajax?

用Ajax实现翻页等内容切换是有缘由的。在传统的无Ajax的站点里,页面A和页面B可能只有10%的地方是不一样的,其余90%的内容(尤为是导航、页脚等公用元素)都是同样的,但却仍然须要浏览器下载并显示新的一整个页面。而若是使用Ajax,不只节省了浏览器须要下载的资源,并且无刷新切换明显比页面跳转更平滑、流畅。浏览器

就视频下面的评论来讲,Ajax能够说是必须的。视频这样的重量级元素,动不动给你从新加载一次,不能忍。服务器

传统的跳转翻页的可取之处

传统的不使用Ajax的站点,每个翻页是一个跳转,而后你能够在浏览器地址栏里看到诸如?page=2这样的参数。每一页就这样经过地址栏的URL作了标记,每一次请求,浏览器都会根据参数返回正确的页码。测试

因此,传统的跳转翻页,刷新也不会丢失状态。优化

结合二者

如今咱们就能够想到,若是在Ajax更新页面局部内容的同时,也在地址栏的URL里更新状态参数,就能够作出更完美的Ajax翻页了。url

不过,JavaScript修改location的除hash外的任意属性,页面都会以新URL从新加载。而惟一不引起刷新的hash参数并不会发送到服务器,所以服务器没法得到状态。spa

而后,HTML5 history API将解决这个问题。设计

介绍HTML5 history API

HTML5 history API只包括2个方法:history.pushState()history.replaceState(),以及1个事件:window.onpopstate

history.pushState()

它的彻底体是 history.pushState(stateObject, title, url),包括三个参数。

第1个参数是状态对象,它能够理解为一个拿来存储自定义数据的元素。它和同时做为参数的url会关联在一块儿。

第2个参数是标题,是一个字符串,目前各种浏览器都会忽略它(之后才有可能启用,用做页面标题),因此设置成什么都不要紧。目前建议设置为空字符串。

第3个参数是URL地址,通常会是简单的?page=2这样的参数风格的相对路径,它会自动以当前URL为基准。须要注意的是,本参数URL须要和当前页面URL同源,不然会抛出错误。

调用pushState()方法将新生成一条历史记录,方便用浏览器的“后退”和“前进”来导航(“后退”但是至关经常使用的按钮)。另外,从URL的同源策略能够看出,HTML5 history API的出发点是很明确的,就是让无跳转的单站点也能够将它的各个状态保存为浏览器的多条历史记录。当经过历史记录从新加载站点时,站点能够直接加载到对应的状态。

history.replaceState()

它和history.pushState()方法基本相同,区别只有一点,history.replaceState()不会新生成历史记录,而是将当前历史记录替换掉

window.onpopstate

push的对立就是pop,能够猜到这个事件是在浏览器取出历史记录并加载时触发的。但实际上,它的条件是比较苛刻的,几乎只有点击浏览器的“前进”、“后退”这些导航按钮,或者是由JavaScript调用的history.back()等导航方法,且切换先后的两条历史记录都属于同一个网页文档,才会触发本事件。

上面的“同一个网页文档”请理解为JavaScript环境的document是同一个,而不是指基础URL(去掉各种参数的)相同。也就是说,只要有从新加载发生(不管是跳转到一个新站点仍是继续在本站点),JavaScript全局环境发生了变化,popstate事件都不会触发。

popstate事件是设计出来和前面的2个方法搭配使用的。通常只有在经过前面2个方法设置了同一站点的多条历史记录,并在其之间导航(前进或后退)时,才会触发这个事件。同时,前面2个方法所设置的状态对象(第1个参数),也会在这个时候经过事件的event.state返还回来。

此外请注意,history.pushState()history.replaceState()自己调用时是不触发popstate事件的。pop和push毕竟不同!

如何应用

HTML5 history API的内容很少,具体如何应用它来改进Ajax翻页呢?

首先,在服务器端添加对URL状态参数的支持,例如?page=3将会输出对应页码的内容(后端模板)。也能够是服务器端把对应页码的数据给JavaScript,由JavaScript向页面写入内容(前端模板)。

接下来,使用history.pushState(),在任一次翻页的同时,也设置正确的带参数的URL。代码多是这样:

newURL = "?page=" + pageNow;
history.pushState(null, "", newURL);

到此,就解决了F5刷新状态还原的事了。

不过,尚未结束,在浏览器中点击后退,例如从?page=3退到?page=2,会发现没有变化。按道理说,这时候也应该对应变化。这就要用到popstate事件了。

window添加popstate事件,加入这种导航变化时的处理。代码多是这样(jQuery):

$(window).on("popstate", function(event) {

    // 取得以前经过pushState保存的state object,尽管本示例并不打算使用它。
    // jQuery对event作了一层包装,须要经过originalEvent取得原生event。
    var state = event.originalEvent.state,

        // 本示例直接取URL参数来处理
        reg = /page=(\d+)/,
        regMatch = reg.exec(location.search),

        // 第1页的时候既能够是 ?page=1,也能够根本没有page参数
        pageNow = regMatch === null ? 1 : +regMatch[1];

    updateByPage(pageNow);
});

这样,就完成了。这样看起来是否会以为还挺容易的呢?在支持HTML5 history API的浏览器中,以上部分就已经作到了带页码记录的Ajax翻页。

有待斟酌的兼容性问题

根据[caniuse][]上的数据,IE10+及其余主流浏览器都支持HTML5 history API。为保证不支持的浏览器不报错,能够加入是否支持HTML5 history API的判断:

// 参考自 http://modernizr.com/download/#-history 源码
var isHistoryApi = !!(window.history && history.pushState);

// ...

if(isHistoryApi){
    // ...
}

这样,一个Ajax翻页,在支持HTML5 history API的浏览器上,将会智能地保存当前页码信息,而不支持的浏览器仍然能够正常使用,只是不保存页码信息(就像改进前那样)。我认为,按照“渐进加强”的思路,这样就是最好的了,也就是:只使用较少的代码优化高级浏览器的使用体验。

若是真的想要在各种浏览器里都表现一致,拥有这样的记录功能呢?

这时候推荐使用Benjamin Lupton的[History.js][],它提供和HTML5 history API近似的api,会在不支持的浏览器里回退到hash形式去处理历史记录。尽管为了兼容这种hash的回退形式你可能要额外作点事(hash不会发送到服务器端),但它确实可让你作到更广范围的兼容。

HTML5 history API并不完美

即便只考虑支持HTML5 history API的浏览器,它们对HTML5 history API的一些细节处理也会有差别和问题。History.js提供的只针对HTML5浏览器的版本,仍然包含了很多处理兼容问题的代码。

可是,不完美也没有关系。以个人测试结果,本文所介绍的简单的写法,就能够在绝大部分支持HTML5 history API的浏览器上正常运行。若是你担忧有哪些浏览器会有潜在问题,去测试那个浏览器就能够了。你最后用于兼容处理的自写代码极可能远比一个JavaScript库少得多,毕竟,你也不必定会喜欢额外引入一个JavaScript库来完成一个功能吧。

相关文章
相关标签/搜索