html无刷新加载页面,ajax无刷新加载页面,结合history.state修改url

在前端开发中,我们很喜欢一种风格,就是页面无刷新,加载新的页面。但是在ajax的加载中,有一个问题,就是页面加载进来很容易,却无法做到在浏览器的前进、倒退时,正确的使用页面。那么,怎么来实现这种风格呢?一般而言,有两种形式,一种是通过hash的变化来确定ajax要加载哪个页面,另一种则是我们这篇文章重点要使用的history.pushState方法。

history.replaceState实现静态化的url改变

这是一个html5的特性,仅高级浏览器可以支持,低版本的浏览器是不支持的,因此我们在文末会讨论兼容方案。

history.state我们会用到四个动作,第一个动作就是history.replaceState。它的作用是使history.state的内容发生变化,这个变化在外在表现来看,就是url的变化:

var url = '/url_to_load',selector = '#content';

$.ajax({

url: url,

type: 'get',

success: function(html) {

var content = $(html).find(selector).html(), title = $(html).find('title').html();

$(selector).html(content);

document.title = title;

var state = {

url: url,

title: title,

selector: selector,

content: content

}

window.history.replaceState(state,title,url);

}

});

在上面这个代码中,我们首先使用到了replaceState这个方法。它的第一个参数就是history.state,我们会在下文用到,如果仅仅是为了实现url的变化,完全可以先无视state参数,把这段代码简化为:

var url = '/url_to_load',selector = '#content';

$.ajax({

url: url,

type: 'get',

success: function(html) {

var content = $(html).find(selector).html(), title = $(html).find('title').html();

$(selector).html(content);

document.title = title;

window.history.replaceState(null,null,url);

}

});

把replaceState方法的第一个和第二个参数都可以设置为null,这样仅仅实现了url的变化。history.replaceState可以在任何时候使用,不一定非得在$.ajax中,一旦通过history.replaceState修改url之后,刷新浏览器页面,就会打开改变后的地址,而与改变之前的页面无关了。

history.pushState向浏览器历史记录中新增状态内容

虽然我们在上面实现了无刷新改变url,但是当我们通过浏览器前进倒退时,却无法做到瞬间打开之前的页面。为了实现这个效果,我们使用到history.pushState,不过需要注意的是,它只是其中必须的一个部分,而不是我们实现这个效果的全部。

在上面的代码中,我们已经发现了replaceState方法的第一个参数是state,它就是一个历史记录的内容,但是它改变的是当前的历史记录的内容。我们可以这么去思考:

url_1 => state_1

url_2 => state_2 // current

一个url对应一个state,这个state的内容是没有任何关系的,除了url这个属性是必须的意外,其他的任何属性,都可以我们自己来规定。而且这个state跟页面的展示几乎没有什么关系(在没有用到的ajax的情况下除外),它就是一个临时的变量,而且是和url一一对应的。

例如我们当前访问的页面是url_2,那么对应的就是state_2,它的内容我们是不知道的。但是当我们执行history.replaceState(state_3,title_3,url_3)的时候,我们原本以为历史记录中变成了:

url_1 => state_1

url_2 => state_2

url_3 => state_3

但是实际上,当前历史记录中的结果却是:

url_1 => state_1url_3 => state_3 // current

也就是当前原本是url_2的内容,被全部替换为了url_3的内容。

当你点击浏览器的返回按钮的时候,页面进入了url_1。怎么才能得到我们想要的结果呢?这时pushState方法就来了。

在执行replaceState之前,我们做这样的一个动作:

var state = {

url: window.loaction.href,

title: document.title,

selector: '#content',

content: $('#content').html()

};

history.pushState(state,state.title,state.url);

这样,就将当前页面的内容加入到历史记录中,也就是上面我们想象的url_1,url_2,url_3的顺序。当执行完replaceState后,点击浏览器的后退按钮,浏览器就调用出来了url_2的内容。所以,我们将上面的代码进行修改,得到:

var url = '/url_to_load',selector = '#content';

$.ajax({

url: url,

type: 'get',

success: function(html) {

var state = {

url: window.loaction.href,

title: document.title,

selector: '#content',

content: $('#content').html()

};

history.pushState(state,state.title,state.url);

var content = $(html).find(selector).html(), title = $(html).find('title').html();

$(selector).html(content);

document.title = title;

var state = {

url: url,

title: title,

selector: selector,

content: content

}

window.history.replaceState(state,title,url);

}

});

通过监听window的popstate事件来确定是否改变页面内容

当完成上面两个步骤之后,我们的浏览器历史记录中,就有了对应的几个url,以及它们对应的state。当我们点击浏览器的前进或后退按钮的时候,url就会发生变化,但是,如果我们不执行下面的动作,页面就不会有任何的改变。

我们在脚本中加入如下的代码:

$(window).bind("popstate", function() {

// 当url发生变化时,你想做什么?

});

通过给window绑定popstate事件监听,可以在点击浏览器的前进和后退时,执行某些动作。

通过history.state取出我们需要的状态内容

但是,我们怎么样才能让我们之前的内容重新恢复到原来的页面中呢?有些人选择的办法是,在popstate事件监听的回调中,再用ajax去请求对应的url内容。这种做法显然的低效的。如果你按照我们上面的代码那样去做,那很好,你需要的内容已经保存在history.state中了。

$(window).bind("popstate", function() {

var state = history.state;

if(state.selector == undefined) return; // 第一次访问页面的时候,selector属性不存在,不用执行动作

if(state.content == undefined) return; // 同上

$(state.selector).html(state.content);

document.title = state.title;

window.history.replaceState(state,state.title,state.url);

});

我们在前面的pushState动作中,通过把selector和对应的content都放到了history.state中,所以,现在把它取出来,直接替换掉页面中的内容即可,包括替换网页标题和selector区域内的html内容。

这里有一个state顺序需要注意一下。比如,我们通过pushState和replaceState,最终得到了如下的history.state列表:

url_1 => state_1

url_2 => state_2

url_3 => state_3 // current

url_4 => state_4

这是,当我们点击后退按钮时,url切换到了url_2,同时在window的popstate事件监听回调函数中,history.state也变为了state_2,而不是state_3。

基于history.state的无刷新页面加载插件制作

既然我们已经知道了这一系列的原理,那么接下来,我们自己来写一个插件,可以方便我们直接在项目中重复使用这个插件,实现我们想要的无刷新页面加载效果。

!function(dependencies,factory){

// amd || cmd

if(typeof define == 'function' && (define.cmd || define.amd)) {

define(dependencies,function() {

return factory();

});

}

else {

var ex = factory();

// CommonJS NodeJS

if(typeof module !== 'undefined' && typeof exports === 'object') {

module.exports = ex;

}

}

}(['jquery'],function(){

$.ajaxPage = function(options) {

var defaults = {

url: window.location.href,

find: '#ajax-body',

replace: '#ajax-body',

beforeSend: null,

success: null,

error: null,

complete: null

};

if(typeof options === 'string') defaults.url = options; // 如果传入的参数是url地址

options = $.extend(defaults,options);

$.ajax({

url: options.url,

beforeSend: function() {

$('html').addClass('ajax-loading');

if(typeof options.beforeSend === 'function') options.beforeSend();

},

success: function(result) {

var state = {

title: document.title,

url: window.location.href,

selector: options.replace,

content: $(options.replace).html()

};

history.pushState(state, state.title, state.url);

var $html = $('

var title = $html.find('title').html();

var content = $html.find(options.find).html();

document.title = title;

$(options.replace).html(content);

// 下面这段代码是为了解决微信中document.title只执行一次的问题,有了这段代码,微信里面的document.title也会跟着一起变化

var ua = navigator.userAgent.toLowerCase();

if(ua.indexOf('micromessenger') > -1 && ua.indexOf('iphone') > -1) {

var $iframe = $('');

$iframe.on('load',function(){

setTimeout(function() {

$iframe.off('load').remove();

},0);

}).appendTo('body');

}

state = {

title: title,

url: options.url,

selector: options.replace,

content: content

};

window.history.replaceState(state, title, options.url);

if(typeof options.success === 'function') options.success(result);

},

error: function(XMLHttpRequest, textStatus, errorThrown) {

if(typeof options.error === 'function') options.error(XMLHttpRequest, textStatus, errorThrown);

if(options.loading) $.hideLoading();

},

complete: function(XMLHttpRequest, textStatus) {

$('html').removeClass('ajax-loading');

if(typeof options.complete === 'function') options.complete(XMLHttpRequest, textStatus);

}

});

};

$(window).bind("popstate", function() {

var state = history.state;

if(state.selector == undefined) return; // 第一次访问页面的时候,selector属性不存在,不用执行动作

if(state.content == undefined) return; // 同上

$(state.selector).html(state.content);

document.title = state.title;

window.history.replaceState(state,state.title,state.url);

});

});

首先,我们使用了omd框架来让插件兼容各种模式下的调用。接着,我们根据上面的原理,写了$.ajaxPage,最后,给window绑定了popstate事件。

使用方法:

首先是了解插件传入值有两种,第一种是url,第二种是对象。如果传入字符串,其他选项将使用默认值,如果传入对象,则可以规定更多内容。

我们来看一个例子:

a.html: 所有的页面的内容,都放在#ajax-body中,把#ajax-body当做body来用,这个地方有点难度,需要功力深一点才能理解为什么

...

javacript1.js:

jQuery(function($){

$(document).on('click','a[href]',function(e) {

e.preventDefault();

var url = $(this).attr('href');

$.ajaxPage(url);

});

});

javacript2.js:

jQuery(function($){

$(document).on('click','a.only-content[href]',function(e) {

e.preventDefault();

var url = $(this).attr('href');

$.ajaxPage({

url: url,

find: '#content',

replace: '#content'

});

});

});

兼容性问题

文章最开始,我们已经预告了末尾要讨论兼容性问题。这里主要是指,在部分浏览器中并不支持history.pushState和history.replaceState的操作,也没有window.popstate事件,这就会使得我们在点击前进后退,会直接打乱我们在这个插件中的一些预期。因此,我们可以采用一些兼容性方案。

首先是判断是否可以使用pushState和replaceState方法:

if(history && history.pushState) {

...

}

在这个地方有一个基本的语法常识,就是&&运算与&运算的区别。如果history=undefined,这个时候直接使用if(history.pushState)进行判断的话,会导致错误而终止程序执行。

如果浏览器不支持history.pushState,那么怎个插件的url和history部分会失效,而ajax部分会起效(当然,会由于报错而终止),所以,就必须采用其他的方法来替代。最好的替代方案就是hash办法,这我们会在另外的文章中去详解。

2016-03-13

7373

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值