location是javascript里边管理地址栏的内置对象,比如location.href就管理页面的url,用location.href=url就可以直接将页面重定向url。而location.hash则可以用来获取或设置页面的标签值。比如http://domain/#admin的location.hash="#admin"。利用这个属性值可以做一个非常有意义的事情。
很多人都喜欢收藏网页,以便于以后的浏览。不过对于Ajax页面来说的话,一般用一个页面来处理所有的事务,也就是说,如果你浏览到一个Ajax页面里边有意思的内容,想将它收藏起来,可是地址只有一个呀,下次你打开这个地址,还是得像以往一样不断地去点击网页,找到你钟情的那个页面。另外的话,浏览器上的“前进”“后退”按钮也会失效,这于很多习惯了传统页面的用户来说,是一个很大的使用障碍。
那么,怎么用location.hash来解决这两个问题呢?其实一点也不神秘。
比如,我的作者管理系统,主要功能有三个:普通搜索、高级搜索、后台管理,我分别给它们分配一个hash值:#search、#advsearch、#admin,在页面初始化的时候,通过window.location.hash来判断用户需要访问的页面,然后通过javascript来调整显示页面。比如:
var hash;
hash=(!window.location.hash)?"#search":window.location.hash;
window.location.hash=hash;
//调整地址栏地址,使前进、后退按钮能使用
switch(hash){
case "#search":
selectPanel("pnlSearch"); //显示普通搜索面板
break;
case "#advsearch":
case "#admin":
}
通过window.location.hash=hash这个语句来调整地址栏的地址,使得浏览器里边的“前进”、“后退”按钮能正常使用(实质上欺骗了浏览器)。然后再根据hash值的不同来显示不同的面板(用户可以收藏对应的面板了),这就使得Ajax页面的浏览趋于传统化了。
用HTML5来控制浏览器地址栏,并支持前进和后退
现在单页无刷新应用里,即用Ajax获取数据,通过前端JS渲染页面,用户在一个页面里完成几乎所有的事情。为了让浏览器记住并标示出当前所处的页面,需要用锚点,即location.hash来记录参数。因为直接修改location.href的话会造成页面跳转。下面介绍的方法是HTML5新增加的方法,可以自由的控制浏览器历史记录,使得地址栏真正改变而不是各种#。
以下为转载:
原文地址:
{
'第一段': 'http://note.sdo.com/u/1185659399/n/6GXE7~jE5sx0LX0xI000pW',
'第二段': 'http://hi.baidu.com/kooboy/blog/item/6f2c31adadedc2134b36d691.html',
'文档': 'https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history'
}
第一段
1. 问题是什么?
可能未写过 Ajax 应用的人会好奇,改变 URL 并且支持后退是这么难的事么?这在现阶段确实是个难题。产生问题的地方主要有两点。
1.1 改变当前 URL 会让浏览器载入页面
Javascript 有一个函数可以改变当前 URL 路径。
window.location = '/'
如果在浏览器的网页调试程序(firebug 或 chrome 原生调试工具)的控制台输入这行代码,页面会马上跳转到当前网站的根目录。这跟用户点击了一个超链接的效果一样。
而如果只改变 URL 的 # 号后面部分,不会导致浏览器重新载入网页。因为浏览器认为 # 号是当前页面的一个锚点,也就不需要刷新了。
window.location = '#here' // 不会导致页面刷新
稍后还会利用到这个特性。
1.2 浏览器不懂得记录 Ajax 调用的状态
因为 Ajax 调用是多种多样的,统计代码会引发 Ajax,广告会引发 Ajax,页面定时刷新会引发 Ajax,所以浏览器并不知道如何记录 Ajax 的状态。
如果用户点击一个链接通过 Ajax 刷新了页面,然后进入其他页面,再然后点了返回按钮,会产生什么状况呢?浏览器会把上次 Ajax 调用返回的数据原封不动的倾倒出来,这可能是 javascript 代码,可能是未处理的 json 数据。一个成熟的网站不会出现这样的情况,因为开发人员已经用各种方法做了处理(后面讨论)。不过你如何在两天前的晚上访问 codecampo.com,有可能会看到这种情况,因为那时候我还不懂怎么处理。: )
1.3 目标
理想中应该让 Ajax 调用达到这样的状态:
如果 Ajax 调用后的页面逻辑上已经跟之前不是同一个页面,那么 URL 应该随之改变。
前提同上,那么在新页面点击浏览器的后退按钮,应该回到 Ajax 调用前的页面。
状态 1 是为了让访客可以将当前网页放入收藏夹,或者通过复制粘贴 URL 分享资源地址;状态 2 是为了符合最小惊讶。
2. 当前主流 - 只改变 URL Hash 的单页面应用
仔细观察可以发现,现在的 Twitter,Google,Facebook 的 URL 地址充斥着 #号或者 #! 号。例如一个新版 Twitter 地址是这样的
https://twitter.com/#!/chloerei
这个 #! 号有什么意义呢?这个可以看看阮一峰整理的这篇《URL的井号》。这里假设你已经了解 # 号后面的改变不会导致页面加载,怎么利用这个特性达到 1.3 提出的目标。
2.1 第一步,有关 Ajax 调用的链接全部用 #path 作为链接目标
例如,如果一个链接本来是
<a href="/topics/1">topic1</a>
就修改为
<a href="#/topics/1">topic1</a>
显然,如果不做后续工作的话,这个链接点击后页面不会发生什么变化,用户也不会被带到新地址。唯一的改变是 URL 的 # 号部分变成了 #topics/1
2.2 设置 onhashchange 事件
在 javascript api 中,窗口 window 对象的 hash 值(# 号后面部分)发生变化时,会调用 onhashchange 事件。给 onhashchange 挂上一个 function,就可以在 hash 有改动的时候调用这个 function。
例如可以在控制台输入这段 js 代码测试
window.onhashchange = function(){alert(window.location.hash)}
实际中 function 里面就是放置真正用来刷新页面的代码了。比如在 jQuery 里用 $.get(location.path)。
2.3 效果
现在的 twitter,gooogle,facebook 都是使用这种方法刷新页面,这对浏览器书签、后退的支持也很好。
但是这有一些副作用。
一是因为页面路径被写在了 Hash 里面,而浏览器是不向服务器发送 Hash 部分的。所以打开这样的 URL 需要两个来回:1、打开空白的首页 2、根据 Hash 用 Ajax 载入实际内容
二是把路径写在了 Hash 里面破坏了 URL 原先的含义。从 URL 字面看
https://twitter.com/#!/chloerei
这个页面表示的是 twitter.com 页面上的 !/chloerie 锚点。但从实际内容上,这表示的是 chloerei 的个人页面。所以有人称这种站点为“单页面应用”。总的来说,这个方案对主流浏览器的支持程度很高,是目前的主流方案。
3. 未来方案:基于 history.pushState API
先看现实中的例子:Github。Github 的源码浏览页面是借助 Ajax,并且正常改变 URL 的例子,浏览器后退功能也正常工作。你可以在 https://github.com/chloerei/campo 点击各个文件夹,同时观察地址栏。不过目前只支持对 HTML5 友好的浏览器,比如 chrome, firefox4。
Github 有篇简短的日志描述了他们的实现方法:https://github.com/blog/760-the-tree-slider
下面再逐步分析一下。
3.1 改变 URL 但不载入页面的方法
HTML5 中新增了 history.pushState 方法,用以向浏览器添加历史记录,但是不触发页面载入。详细的文档可以看这里。
基本用法就是在 Ajax 发送的同时,将访问的地址用 pushState 方法加入页面历史。如果你用 Rails 的 ujs-jquery 方式调用 Ajax,看起来是这样的(campo项目的部分代码)
HTML 部分
<div class="paginate"><a href="/?page=1" data-remote="true">下一页</a></div>
data-remote 属性是 ujs 用来启用 ajax 的标志位。
js 部分
$('.paginate a').live('ajax:beforeSend', function(event, xhr, settings) { if (history && history.pushState) {
history.pushState(null, document.title, this.href);
}
});
这个钩子方法,在发送 ajax 请求之前把目标地址推进浏览器的历史记录,于是浏览器的地址栏更新但不重载整个页面。
3.2 处理后退按钮
浏览器后退的时候会触发 onpopstate 事件,所以要给这个事件挂上处理方法。
if (history && history.pushState) {
$(window).bind("popstate", function() { $.getScript(location.href);
});
}
这里的逻辑跟URL Hash 的单页面应用的方法很类似,不同的是之前处理的是 hashchange,这里处理的是 popstate。
3.3 从非 Ajax 页面返回到 Ajax 页面
做完上面两步,已经可以在 Ajax 页面来回切换了,但是如果进入了一个非 Ajax 页面,然后按了后退,这时候就会把之前 Ajax 获取的代码全部倒出来,因为上下文已经切换了,浏览器不知道怎么处理这些代码。
这时候要做两个处理
1)要求 Ajax 相关页面不缓存,如果你用 Rails,你可以看这篇文章: http://blog.serendeputy.com/posts/how-to-prevent-browsers-from-caching-a-page-in-rails/
2)处理 popstate 的方法增加一个标志位,第一次载入页面的时候不要调用后面的逻辑。
if (history && history.pushState) {
var loaded = false;
$(window).bind("popstate", function() { if (!loaded) {
loaded = true;
} else {
$.getScript(location.href);
}
});
}
注意 loaded 这个变量的作用。因为之前已经把页面缓存关了,如果不设置这个标志位,浏览器后退的时候就会既载入页面,也触发 popstate 事件,导致二次载入。
第二段
history.pushState/replaceState 方法
熟悉JavaScript开发的同学,对History肯定不会陌生,其中最经典的方法就是go,通过第一个类型为整数的传输参数,可以使浏览器到达当前页面之前或之后,曾经浏览过的页面。当然,这个也是要刷新来实现的。
现在History API新增了两个方法,分别是pushState和replaceState,其实用法都一样,看Mozilla的文档也没看到它们有多大不同,哈哈。
用法如下:
var state = { //这里可以是你想给浏览器的一个State对象,为后面的StateEvent做准备。
title : "HTML 5 History API simple demo",
url : "yourpage"
};
history.pushState(state, "HTML 5 History API simple demo", "yourpage");
还算简单吧,那么replaceState也是同样的用法:
var state = { //这里可以是你想给浏览器的一个State对象,为后面的StateEvent做准备。
title : "HTML 5 History API simple demo",
url : "yourpage"
};
history.replaceState(state, "HTML 5 History API simple demo", "yourpage");
State Event
既然有无刷新改变URL的方法,当然也要有响应这个改变的时间啦。
嗯,没错。就有一个popstate事件,而传入的handler函数有一个参数,就是之前在pushState的第一个参数,一个State obj。开发者可以通过这个State obj来识别行为。详细代码如下:
var state = { //这里可以是你想给浏览器的一个State对象,为后面的StateEvent做准备。
title : "HTML 5 History API simple demo",
url : "yourpage"
};
history.pushState(state, "HTML 5 History API simple demo", "yourpage");
window.onpopstate = function (e) { document.title = e.state.title;}
当然还可以这样:
var state = { //这里可以是你想给浏览器的一个State对象,为后面的StateEvent做准备。
action : "page",
title : "HTML 5 History API simple demo",
url : "yourpage"
};
history.pushState(state, "HTML 5 History API simple demo", "yourpage");
window.onpopstate = function (e) {
switch (e.state.action) {
case "home" :
document.title = "HOME ……";
$.get("index.php").done(function (data) {
$("#wrapper").html(data);
});
break;
case "page" :
document.title = e.state.title;
$.get(e.state.url).done(function (data) {
$("#wrapper").html(data);
});
break;
}
}