I'm Sun

如何写一个无刷新的网站(三)无刷新地改变 URL

上一篇里我们讲了如何写一个路由器对 URL 进行解析,下面的任务是如何改变当前 URL 而不引起页面跳转以及如何监测 URL 的改变。

针对不同版本的浏览器,这里有两种解决方案。

对于低版本浏览器,我们通过改变 URL 的 hash 部分来改变 URL,比如:

1
2
<a href="#/example/1">example 1</a>
<a href="#/example/2">example 2</a>

假设当前 URL 为 www.imsun.net/path/,点击第一个链接后就会变成 www.imsun.net/path/#/example/1,而且显然这不会引起页面跳转。

那么如何监测 URL 的改变呢?BOM 中提供了一个 hashchange 事件,每当 hash 改变时就会触发,我们只要向这个事件添加监听就好了。

结合上一篇中写的 Router:

1
2
3
window.addEventListener('hashchange', funtion(){
Router.checkURL(location.hash);
});

某些更低版本的浏览器(如 IE6)不支持 hashchange 事件,需要手动检测 hash,就没有必要支持了。

对于高版本浏览器,HTML5 新的 history API 提供了一个 pushState() 方法,下面是 MDN 的描述:

pushState() takes three parameters: a state object, a title (which is currently ignored), and (optionally) a URL. Let’s examine each of these three parameters in more detail:

  • state object — The state object is a JavaScript object which is associated with the new history entry created by pushState(). Whenever the user navigates to the new state, a popstate event is fired, and the state property of the event contains a copy of the history entry’s state object.

The state object can be anything that can be serialized. Because Firefox saves state objects to the user’s disk so they can be restored after the user restarts her browser, we impose a size limit of 640k characters on the serialized representation of a state object. If you pass a state object whose serialized representation is larger than this to pushState(), the method will throw an exception. If you need more space than this, you’re encouraged to use sessionStorage and/or localStorage.

  • title — Firefox currently ignores this parameter, although it may use it in the future. Passing the empty string here should be safe against future changes to the method. Alternatively, you could pass a short title for the state to which you’re moving.

  • URL — The new history entry’s URL is given by this parameter. Note that the browser won’t attempt to load this URL after a call to pushState(), but it might attempt to load the URL later, for instance after the user restarts her browser. The new URL does not need to be absolute; if it’s relative, it’s resolved relative to the current URL. The new URL must be of the same origin as the current URL; otherwise, pushState() will throw an exception. This parameter is optional; if it isn’t specified, it’s set to the document’s current URL.

总之这个函数允许我们改变当前 URL 而不发生跳转,就是我们想要的结果。

如何监测 URL 的改变呢?HTML5 同样提供了一个 popstate 事件,使得我们可以这样:

1
2
3
window.addEventListener('popstate', fucntion() {
Router.checkURL();
});

这样就完成了 URL 操作部分,我的开源项目 PRouter.js 也包括了这一部分的功能。有能力的可以去读 Backbone 源码的 history 部分。

结合前面的路由器,我们现在已经可以实现一个完整无刷新站点了。需要注意的一点是,不要在路由的回调函数里对 URL 进行操作,否则会引起多重解析甚至无限循环。