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;
}
}