端午六一休息三天宅了一天, 独自去市中心里转了一天, 就剩下一天了[惊恐]. 压根就没有马上要考试了的感觉嘛! 文章也是好久没更新了, 浑浑噩噩过下去曾的好嘛>.O. 好嘛, 那就来写点干货好了, 也是最近在搞Ghost, 把这个主题的逻辑 (原先是 jQ ) 用原生 js 重写了一遍 (还顺便搞了个小项目 GhostBot 来处理站内搜索), 总结总结研究出了些什么好了, 废话讲得多, 直接进正题吧.
##PJAX
关于题目抛出的这个问题, 我既然都这么问了, 正常人都知道答案肯定是不是
咯. 其实AJAX这个东西一直以来都是被当做只能处理小范围的, 异步的页面更新, 它的优点就在于可以实现页面的无刷新操作. 但是这也会造成一些问题, 比如对搜索引擎兼容性不好啦 (抓不到 ajax 载入的信息), 还有无法后退神马的!
对于无法前进后退, 邪恶的 W3C 就在 HTML5 中搞了一个叫history对象
的东西, 使用 history 对象的pushState
方法给浏览器的浏览历史中塞入一个地址, 返回的时候再借助popstate事件
来还原到原先的模样, 这样一来 AJAX 和 pushState 就开始名正言顺地搞基了! 于是我们就把这俩基友称为PJAX
.
##(正常情况下) 它是如何工作的
这个东西其实不是什么新生事物, 国内在 2012 年就已经对它有一些提及, 只是没有流行开而已, 经常上 GitHub 和 Youtube 的人不知道有没有发现这俩其实早就使用PJAX
了.
HTML5 新增的方法在这里:
- history.pushState(state, title [, url]) : 向历史记录(把它看做一个栈)的顶端加入一条记录,
state
是一个指明状态的对象, 如{url: 'xxxxx', title: 'yyyyy', myInfo: 'zzzzz'}
; - history.replaceState(state, title [, url]) : 跟上面差不多, 功能就是替换历史记录顶上的记录;
1 | //当页面准备改变时 |
响应前进后退的事件:
- window.onpopstate : 上面传递的state对象会成为event的子对象,这样就可以拿到存储的title和URL了. 这个事件在我所测试的 Chrome 36dev 和 Safari 7 中表现有差异, 具体在页面首次载入时, Chrome 不会触发这一事件而 Safari 会触发.
1 | window.onpopstate = function (ev) { |
国内也早就有很多开源的 PJAX 插件, 基于 jQuery, qwrap, kissy 什么的, 但是貌似我没有找到原生的啊, 反正到最后还是自己写了个渣渣的.
###前端处理
首选我们需要一个容器 (container) 来做页面中需要改变的部分. 然后将不需要改变的部分, 比如 footer, header 放在容器的外面.
对于页面上所有的超链接 (a标签), 我们都需要采取 hijack(劫持) 的方式来处理点击事件.
1 | var a = document.getElementsByTagName('a'); |
差异于一般的 AJAX 返回 JSON, PJAX 的后端返回的一般是 HTML 片段. 前台再直接使用 innerHTML 来完成页面无刷. 对于同一个 URL, 如何告诉后端这是一个 PJAX 请求呢? So easy! 直接在 HTTP 请求头上带上标记就好了. 在前端 Ajax 中带上
1 | setRequestHeader(‘PJAX’, ‘true’); |
###后端处理
接上面的话, 直接在后台判断 HTTP 头就行了 (以 PHP 为例):
1 | <?php |
##为什么要用它呢
简而言之, PJAX = AJAX + history.pushState. 这货不仅可以无刷新地改变页面内容, 还可以改变 URL 和浏览器历史记录, 使得前进后退的体验一体化.
Wow, 不明觉厉的样子也.
好吧, 那我们上个图来看看, 以 GitHub 为例.
如下图, 我首先打开了一个 (我的0.0) GitHub 主页, 可以看到浏览器的历史记录是空的 (灰色);
然后我点击了 Contributions 按钮. 页面改变了, 但是页面并没有刷新
, 看样子是 PJAX 基佬起作用了! 浏览器的历史记录里面出现了刚才访问的位置, 地址栏也变化了, 下面的调试窗口显示有三条 XHR (XMLHttpRequest) 请求成功.
还是不明白? 自己去 GitHub.com 看个究竟把!
噢对了, 还没说为什么要用 PJAX 呢.
- 无刷新, 速度快 (快就是王道)
- 无刷新意味着可以在页面改变的时候施展各种绚丽的动画 (漂漂的, 暖暖的)
- 对搜索引擎支持好, 有利于 SEO
- 我™就是喜欢它怎么着, 打我呀
顺便说说弊端.
- 逻辑比较复杂. (跟正常方式不一样的都叫复杂)
- 需要前后台配合. (这点后面再讲, 其实可以克服)
- 跨域限制. (其实这是安全原则, 也可以不算弊端拉)
- 兼容性什么就不提了, 不用优雅的 Chrome 的都别来我的 blog 就对了.
##我的方案 (不正常情况)
本站就使用了全站内链的 PJAX 无刷, 并开启了 10 分钟的 localStorage, 以尽量减少 HTTP 请求.
因为Ghost的主题只是提供了 hbs 模板文件和静态资源文件, 我并不能通过修改 Ghost 内核的方式来使得输出 HTML 片段. 所以只能通过解析 html 的方式来进行 (电脑牛逼, 不怕卡, 打我呀).
1 | var ajax = new Ajax(); //我封装的一个Ajax类 |
这样就减少了一个弊端, 就是可以不修改后台直接在前台做好所有的工作.
##兼容性
直接看图吧
- IE10+
- Chrome 5+
- Safari 5+
- Opera 11.5+
- Firefox 4+