ajax+history.pushState简单实现页面不跳转刷新
又到了一年一折腾blog的时候了,这次简单搞一下以前想弄的一个小功能:点击a链接,页面不跳转,通过ajax获取页面内容,刷新页面
这么做主要是可以提高页面展现速度,从当前页面在站内跳转的时候,减少资源请求
原理很简单:
- 点击a链接,分析链接地址,站外直接跳走,站内往下
- 阻止默认事件,ajax获取链接地址的html内容
- 取到内容里特定的节点,替换到当前页面特定节点里
- 取到内容里的title,更新页面title
- 利用history.pushState更新浏览器地址栏里的地址
我这里用的是一种简化的方案,更完整的方案其实是有服务端支持,它也有一个名字叫pjax,我的blog内容简单,所以按我上面的描述简单实现了一个,js内容如下:
loadJs('/js/jquery-1.7.2.min.js', function() {
(function($) {
//必须支持pushstate
var support =
window.history && window.history.pushState && window.history.replaceState &&
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
var cache = {}
var request = function(url, callback, isCache) {
var defaultJump = function() {
location.href = url
}
var success = function(data) {
try {
if (isCache) {
cache[url] = data
}
callback && callback(data)
} catch (e) {
defaultJump()
}
}
if (isCache) {
cache[url] && success(cache[url])
}
$.ajax({
url: url,
error: defaultJump,
success: success
})
}
var getContent = function(data, opts) {
var body = data.match(opts.contentReg || /<body>[\s\r\n]+([\s\S]+)<\/body>/i)[1]
var title = data.match(opts.titleReg || /<title>([\s\S]+)<\/title>[\s\r\n]+/i)[1]
return {
body: body,
title: title
}
}
var setContent = function(content, opts) {
$(opts.container).html(content.body)
document.title = content.title
}
var pushState = function(state) {
history.pushState(state, state.title, state.url)
}
$.fn.ajaxLoadPage = function(opts) {
if (!support) {
return this
}
if (!opts.container) {
opts.container = this
}
window.onpopstate = function(event) {
var state = event.state
if (!state) {
return
}
request(state.url, function(data) {
var content = getContent(data, opts)
setContent(content, opts)
opts.callback && opts.callback()
}, opts.cache)
}
//first init, replace current state
history.replaceState({
url: location.href,
title: document.title
}, document.title)
return this.on('click', opts.selector || 'a', function(event) {
var link = event.currentTarget
if (location.protocol !== link.protocol || location.hostname !== link.hostname) {
return
}
event.preventDefault()
var url = link.href
request(url, function(data) {
var content = getContent(data, opts)
setContent(content, opts)
pushState({
url: url,
title: content.title
})
opts.callback && opts.callback()
}, opts.cache)
})
}
})(jQuery)
$('body').ajaxLoadPage({
cache: true,
callback: function() {
window.DUOSHUO && window.DUOSHUO.init()
}
})
})
上面的代码其实是第四版,第一版的代码在popstate的时候直接用的是链接跳转时的回调,在浏览器前进后退的时候会有bug,第二版的代码修复了这个bug,第三版的代码加了ajax的内存cache,blog是部署在github上,有时候会有点慢,尽量利用上一次请求的结果,至于localStorage的缓存,暂时不考虑了,这个还需要缓存的时间及更新问题,没必要搞那么复杂,第四版的代码第一次打开再后退没有state的bug。
同时还需要改造一下loadJs
函数,让它拥有缓存功能,这样就不必每次更新页面的时候重新加载资源
(function(exports) {
var cache = {}
function loadJs(src, callback) {
if (!cache[src]) {
cache[src] = {
state: 'init',
queue: []
}
}
if (cache[src].state == 'ready') {
callback()
} else {
cache[src].queue.push(callback)
}
var doc = document;
var head = doc.getElementsByTagName('head')[0];
var s = doc.createElement('script');
var re = /^(?:loaded|complete|undefined)$/;
s.onreadystatechange =
s.onload =
s.onerror = function() {
if (re.test(s.readyState)) {
cache[src].state = 'ready'
var queue = cache[src].queue
for (var i = queue.length - 1; i >= 0; i--) {
queue[i]()
queue.pop()
}
s.onload = s.onerror = s.onreadystatechange = null
s = null
}
}
s.src = src
s.async = true
head.insertBefore(s, head.firstChild)
}
exports.loadJs = loadJs
})(this)
问题列表
有js会不正常执行
多说的脚本好像是只初始化一次,内部可能有某些检查,没有细看它的代码,可以通过传入callback
来解决,如上面的代码
//调用刚才写的插件
$('body').ajaxLoadPage({
callback: function() {
window.DUOSHUO && window.DUOSHUO.init()
}
})