HTML5,让 Ajax 改变 URL 且支持后退

来源:互联网 发布:淘宝能发货到台湾吗 编辑:程序博客网 时间:2024/06/01 08:55

转自:http://codecampo.com/topics/84

如果想对 HTML5 有进一步了解,强烈推荐 《DIVE INTO HTML5》 这本书。

HTML5 并不是魔法,它一方面对现存的 Web 的事实标准做出总结,一方面对未来做出展望。用上 html5 不会一下子让网站小丑变天鹅,但会让 Web 应用的开发更优雅。这次我分享一下如何用 HTML5 的方法让 Ajax 应用可以改变用户的 URL 地址栏并且支持后退按钮。

HTML5

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 调用达到这样的状态:

  1. 如果 Ajax 调用后的页面逻辑上已经跟之前不是同一个页面,那么 URL 应该随之改变。
  2. 前提同上,那么在新页面点击浏览器的后退按钮,应该回到 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 事件,导致二次载入。

3.4 缺点

缺点也是很明显的,目前只有 Chrome,Firefox4,safari支持 pushState。具体支持程度可以看这个网页,http://caniuse.com/#search=pushState (注意这个页面是用 Hash方法),世界范围的支持程度是21%。

结语

现在 codecampo.com 已经开始使用这种方法进行翻页,各位可以在首页测试翻页效果。

在实现过程中,深深觉得这些处理方法还是很麻烦。我希望有一天,HTML5 API 直接提供一个 history.save 方法,将整个页面状态保存下来。

有谁知道更好的协调 AJAX 调用和浏览器历史记录/后退按钮的方法,欢迎留言。

0 0
原创粉丝点击