使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

 

我们使用location.hash来模拟ajax的前进后退功能。使用location.hash存在下面几个问题:

1.使用location.hash会导致地址栏的url发生变化,用户体验不够友好。

2.location.hash产生的历史记录无法修改,每次hash改变都会导致产生一个新的历史记录。

3.location.hash只是1个字符串,不能存储很多状态相关的信息。

为了解决这些问题,HTML5中引入了history.pushState()、history.replaceState()、popstate事件来处理浏览器历史记录的问题。下面的代码可以达到跟location.hash相同的效果,可以看到地址栏url不会改变。

 

效果如下:实例DEMO 1   |   实例DEMO 2    |   实例DEMO 3

使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现
使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

HTML5 新增的历史记录 API 可以实现无刷新更改地址栏链接,配合 AJAX 可以做到无刷新跳转。

简单来说:假设当前页面为justcode.ikeepstudying.com,那么执行下面的 JavaScript 语句:

window.history.pushState(null, null, "/profile/");

之后,地址栏的地址就会变成justcode.ikeepstudying.com/profile/,但同时浏览器不会刷新页面,甚至不会检测目标页面是否存在。

 

state是什么?

状态对象(state object) — 一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,popstate事件都会被触发,并且事件对象的state属性都包含历史记录条目的状态对象的拷贝。(原文

说白了,state就是我们用于保存到history序列中的一个数据集合。我们可以在pushState之前,先构建自己的state,里面可以存放很多我们在监听window.popstate事件时所需要的东西。

例如,我们用代码来看:

var state = {
  title: title,
  url: url,
  content: $('body').html(),
  prev: window.location.href,
  time: (new Date()).getTime()
};
history.pushState(state, title, url);

 

上面title和url两个值是其他地方已经准备好的。通过上面这段代码,在history序列中就多出一个state,并且可以通过window.popstate识别。

而在window.popstate的监听动作中,我们可以这样去做:

window.onpopstate = function(event) {
  var state = history.state; // 等价于 
  var state = event.state;
  if(state && state.content) $('body').html(state.content);
};

 

当点击浏览器的前进或后退按钮时,popstate事件被触发,浏览器会到history序列中去找到上一个或下一个state,并且将这个state放到event.state中。这个时候,该state就是当前浏览器所显示的页面所使用的state了,所以使用history.state可以读取到同样的内容。

而这个时候,该state中,正好保存着我们在pushState时,保存到content内容,直接使用它即可。为了实现title,css,js之类的进一步重载,你还可以做更多的内容保存和逻辑处理。

从上面可以看到,当我们在使用pushState和popstate时,可以在脑海中先想象一个history序列,通过pushState加入一个新state到序列中,通过history.state取出当前屏幕显示区域所需要的state。

 

replaceState和pushState的区别

虽然从上面的讲解中,我们大致掌握了state序列的问题,但是,我们还有一个点没有讲到,那就是改变url。毫无疑问,我们知道通过replaceState可以无刷新页面改变url。

history.replaceState(state,title,url);

其中,state,title,url都是我们事先准备好的。其中,要点就是url,如果我们仅仅希望实现URL的变化,甚至可以做下面这个实验:

history.replaceState(null,null,'/test.html');

把这段代码加入到页面后,观察url的变化。实际上,整个页面没有发生任何变动,唯独url改变了。

这个时候,我们回头来看replaceState之后,state的一些变化。通过下面这段代码来试验:

console.log(history.state); // 得到一个内容丰富的state内容
history.replaceState(null,null,'/test.html');
console.log(history.state); // 得到null

这说明replaceState将对当前显示界面对应的state发生作用,甚至替换state的内容,这是一个会产生负影响的操作,实际上,我们希望通过pushState将一个配置好的state加入到history序列中,但是replaceState改变了我们加入到序列中的state的内容。因此,我们必须保证replceState的时候,所传入的state参数所我们想要使用的state的内容。这个很难理解,我们用一张图来解释:

上图中,1是原本的history序列,我们在阅读到某个界面到时候,执行pushState操作,使history序列变成了2的状态,再对pushState的state执行replaceState后,变成了3的状态。从2变为3这个过程,把原本的state修改了,我们要想清楚,我们是要把{title,content}放入history还是把{name,number}放入history,如果是前者,那么就要在replaceState的时候,格外小心。

 

配合ajax实现无刷新切换url的页面跳转

众所周知,Ajax可以实现页面的无刷新操作——优点;但是,也会造成另外的问题,无法前进与后退!曾几何时,Gmail似乎借助iframe搞定,如今,HTML5让事情变得如同过家家般简单。

当执行Ajax操作的时候,往浏览器history中塞入一个地址(使用pushState)(这是无刷新的);于是,返回的时候,通过URL或其他传参,我们就可以还原到Ajax之前的模样。

本demo所展示的就是ajax的内容载入与地址栏的前进与后退,典型应用,对于熟悉相关知识点很有帮助。

您可以狠狠地点击这里:HTML5 history API与ajax分页实例

 

demo结构大致如下:左边导航菜单,右侧详细内容。

使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现
使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

如果我们想偷懒,导航直接URL地址,点击刷新得了。但头尾内容都是一样的,刷新总显得浪费。从体验上讲,点击导航,右侧Ajax局部刷新是更优的策略。

Ajax局部刷新小菜,稍有经验都能轻松应对。现在如果提出如下需求:每次ajax刷新就如果页面刷新一样,可以后退查看之前内容,怎么破?

我的策略如下:

  • 每次手动点击左侧的菜单,我将Ajax地址的查询内容(?后面的)附在demo HTML页面地址后面,使用history.pushState塞到浏览器历史中。
  • 浏览器的前进与后退,会触发window.onpopstate事件,通过绑定popstate事件,就可以根据当前URL地址中的查询内容让对应的菜单执行Ajax载入,实现Ajax的前进与后退效果。
  • 页面首次载入的时候,如果没有查询地址、或查询地址不匹配,则使用第一个菜单的Ajax地址的查询内容,并使用history.replaceState更改当前的浏览器历史,然后触发Ajax操作。

 

于是,你会看到:

  • 页面首次载入,虽然我们访问的URL的后缀是光秃秃的.html,但是,实际URL最后是:
    使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现
    使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

    因为被history.replaceState摆了一道。

  • 鼠标点击左边的任意一个菜单,会发现,右侧内容虽然是Ajax载入,但是,页面的URL地址却变了,例如,点击宝山区:
    使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现
    使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

    因为历史记录被history.pushState插了一刀。

  • 此时,我们点击地址栏的后退按钮,就是这个:
    使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现
    使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

    奇迹般的,页面无刷新的,又回到了浦东菜单:

    使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现
    使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

    因为window.onpopstate让他又出来。

HISTORY API

打开浏览器的控制台并输入window.history或简单地输入history。在现代浏览器中,您应该看到以下内容:

使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现
使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

PUSHSTATE

将给定状态推送到会话历史记录,具有给定标题,以及可选的给定URL。

句法:

history.pushState(state, title, url);

参数:

state 状态对象是表示用户界面状态的对象。当用户导航到新状态时,popstate将触发事件,并且state事件的属性等于历史状态对象。
title(目前被忽略)state的头衔。
url  (可选)状态的URL。绝对和相对URL都被接受。
history.pushState(null, null, 'new-page.html');
// or
history.pushState({url: 'new-page.html'}, 'New Page');
// or
history.pushState({url: 'new-page.html', page: 2}, 'New Page', 'new-page.html');

 

POPSTATE事件

每次当前历史记录条目更改时,都会触发popstate事件。当这种情况发生在用户点击浏览器的后退/前进按钮或编程调用history.back()hitory.forward()history.go()方法。

// jQuery
$(window).on('popstate', function (e) {
    var state = e.originalEvent.state;
    if (state !== null) {
        //load content with ajax
    }
});

// Vanilla javascript
window.addEventListener('popstate', function (e) {
    var state = e.state;
    if (state !== null) {
        //load content with ajax
    }
});

 

工作实例

下一个示例演示了真正的单页应用程序应该是什么样子。查看我们的工作pushState演示。源代码可在此GitHub仓库中找到

<script>
$(function () {
    var load = function (url) {
        $.get(url).done(function (data) {
            $("#content").html(data);
        })
    };

    $(document).on('click', 'a', function (e) {
        e.preventDefault();

        var $this = $(this),
            url = $this.attr("href"),
            title = $this.text();

        history.pushState({
            url: url,
            title: title
        }, title, url);

        document.title = title;

        load(url);
    });

    $(window).on('popstate', function (e) {
        var state = e.originalEvent.state;
        if (state !== null) {
            document.title = state.title;
            load(state.url);
        } else {
            document.title = 'World Regions';
            $("#content").empty();
        }
    });
});
</script>

<a href="africa">Africa</a>
<a href="asia">Asia</a>
<a href="europe">Europe</a>
<a href="north-america">North America</a>
<a href="oceania">Oceania</a>
<a href="south-america">South America</a>
		
<div id="content"></div>

 

单页应用SEO

您应该将请求与服务器区分开来,并将适当的内容发送到浏览器。例如:当通过XMLHttpRequest(AJAX请求)发出请求时,响应应仅包含所请求的内容(网页的特定部分)。对于经常要求你必须把整个网页,例如htmlhead, body+页面内容。

 

为什么PUSHSTATE而不是HASH-BANGS

  • 清除没有破坏RFC 3986的URL
  • 甚至没有JavaScript的工作
  • 不太关心抓取和索引

总之,由于现代浏览器被广泛使用,您应该考虑使用HTML5 pushState来构建您的单页应用程序,并且#!如果您打算支持旧浏览器,请使用hash-bang方法。实际上,Facebook使用了一种双重方法 – 用于IE9的hash bang和用于现代浏览器的pushState。

 

history.pushState

history.pushState({}, "页面标题", "xxx.html");

history.replaceState

history.replaceState(null, "页面标题", "xxx.html");

window.onpopstate

window.addEventListener("popstate", function() {
    var currentState = history.state;
    /*
     * 该干嘛干嘛
    */
});

 

浏览器兼容性

根据 MDN 提供的信息,IE 10, Chrome 5, Firefox 4, Safari 5 开始支持这个特性。Fallback 可以采用替换 hash 的方法。另外,History.js 库也提供了对老版本浏览器的 history API 支持(同样是利用替换 hash)。为了搜索引擎收录,可能需要使用#!表示法。

Chrome Safari Firefox Opera IE Android iOS
31+ 7.1+ 34+ 11.50+ 10+ 4.3+ 7.1+

 

效果如下:实例DEMO 1   |   实例DEMO 2    |   实例DEMO 3  

 

本文:使用pushState()改变url而不刷新, ajax与HTML5 history pushState/replaceState实例, 详解history.pushState和history.replaceState以及page ajax的实现

One Comment

Leave a Reply