1. 程式人生 > >轉:HTML5 History API 詳解

轉:HTML5 History API 詳解

從Ajax翻頁的問題說起

請想象你正在看一個視訊下面的評論,在翻到十幾頁的時候,你發現一個寫得稍長,但非常有趣的評論。正當你想要停下滾輪細看的時候,手殘按到了F5。然後,頁面重新整理了,評論又回到了第一頁,所以你又要重新翻一次。

再或者,你想把這個評論發給別人分享,一面給了別人頁面地址(為什麼不直接複製呢?因為要連帶視訊等場景啊),一面又要加一句囑咐:請翻到下面評論的第XX頁的XX樓。

這就是問題。試想一下,如果瀏覽器能記住你當前的狀態(比如看到了第十幾頁),而不是一重新整理就還原,是不是就顯得智慧多了?

為什麼用Ajax?

用Ajax實現翻頁等內容切換是有原因的。在傳統的無Ajax的站點裡,頁面A和頁面B可能只有10%的地方是不同的,其他90%的內容(尤其是導航、頁尾等公用元素)都是一樣的,但卻仍然需要瀏覽器下載並顯示新的一整個頁面。而如果使用Ajax,不僅節省了瀏覽器需要下載的資源,而且無重新整理切換明顯比頁面跳轉更平滑、流暢。

就視訊下面的評論來說,Ajax可以說是必須的。視訊這樣的重量級元素,動不動給你重新載入一次,不能忍。

傳統的跳轉翻頁的可取之處

傳統的不使用Ajax的站點,每一個翻頁是一個跳轉,然後你可以在瀏覽器位址列裡看到諸如?page=2這樣的引數。每一頁就這樣通過位址列的URL做了標記,每一次請求,瀏覽器都會根據引數返回正確的頁碼。

所以,傳統的跳轉翻頁,重新整理也不會丟失狀態。

結合兩者

現在我們就可以想到,如果在Ajax更新頁面區域性內容的同時,也在位址列的URL裡更新狀態引數,就可以做出更完美的Ajax翻頁了。

不過,JavaScript修改location的除hash外的任意屬性,頁面都會以新URL重新載入。而唯一不引發重新整理的hash

引數並不會傳送到伺服器,因此伺服器無法獲得狀態。

然後,HTML5 history API將解決這個問題。

介紹HTML5 history API

HTML5 history API只包括2個方法:history.pushState()history.replaceState(),以及1個事件:window.onpopstate

history.pushState()

它的完全體是 history.pushState(stateObject, title, url),包括三個引數。

第1個引數是狀態物件,它可以理解為一個拿來儲存自定義資料的元素。它和同時作為引數的url

會關聯在一起。

第2個引數是標題,是一個字串,目前各類瀏覽器都會忽略它(以後才有可能啟用,用作頁面標題),所以設定成什麼都沒關係。目前建議設定為空字串。

第3個引數是URL地址,一般會是簡單的?page=2這樣的引數風格的相對路徑,它會自動以當前URL為基準。需要注意的是,本引數URL需要和當前頁面URL同源,否則會丟擲錯誤。

呼叫pushState()方法將新生成一條歷史記錄,方便用瀏覽器的“後退”和“前進”來導航(“後退”可是相當常用的按鈕)。另外,從URL的同源策略可以看出,HTML5 history API的出發點是很明確的,就是讓無跳轉的單站點也可以將它的各個狀態儲存為瀏覽器的多條歷史記錄。當通過歷史記錄重新載入站點時,站點可以直接載入到對應的狀態。

history.replaceState()

它和history.pushState()方法基本相同,區別只有一點,history.replaceState()不會新生成歷史記錄,而是將當前歷史記錄替換掉。

window.onpopstate

push的對立就是pop,可以猜到這個事件是在瀏覽器取出歷史記錄並載入時觸發的。但實際上,它的條件是比較苛刻的,幾乎只有點選瀏覽器的“前進”、“後退”這些導航按鈕,或者是由JavaScript呼叫的history.back()等導航方法,且切換前後的兩條歷史記錄都屬於同一個網頁文件,才會觸發本事件。

上面的“同一個網頁文件”請理解為JavaScript環境的document是同一個,而不是指基礎URL(去掉各類引數的)相同。也就是說,只要有重新載入發生(無論是跳轉到一個新站點還是繼續在本站點),JavaScript全域性環境發生了變化,popstate事件都不會觸發。

popstate事件是設計出來和前面的2個方法搭配使用的。一般只有在通過前面2個方法設定了同一站點的多條歷史記錄,並在其之間導航(前進或後退)時,才會觸發這個事件。同時,前面2個方法所設定的狀態物件(第1個引數),也會在這個時候通過事件的event.state返還回來。

此外請注意,history.pushState()history.replaceState()本身呼叫時是不觸發popstate事件的。pop和push畢竟不一樣!

如何應用

HTML5 history API的內容不多,具體如何應用它來改進Ajax翻頁呢?

首先,在伺服器端新增對URL狀態引數的支援,例如?page=3將會輸出對應頁碼的內容(後端模板)。也可以是伺服器端把對應頁碼的資料給JavaScript,由JavaScript向頁面寫入內容(前端模板)。

接下來,使用history.pushState(),在任一次翻頁的同時,也設定正確的帶引數的URL。程式碼可能是這樣:

newURL = "?page=" + pageNow;
history.pushState(null, "", newURL);

到此,就解決了F5重新整理狀態還原的事了。

不過,還沒有結束,在瀏覽器中點選後退,例如從?page=3退到?page=2,會發現沒有變化。按道理說,這時候也應該對應變化。這就要用到popstate事件了。

window新增popstate事件,加入這種導航變化時的處理。程式碼可能是這樣(jQuery):

$(window).on("popstate", function(event) { // 取得之前通過pushState儲存的state object,儘管本示例並不打算使用它。 // jQuery對event做了一層包裝,需要通過originalEvent取得原生event。 var state = event.originalEvent.state, // 本示例直接取URL引數來處理 reg = /page=(\d+)/, regMatch = reg.exec(location.search), // 第1頁的時候既可以是 ?page=1,也可以根本沒有page引數 pageNow = regMatch === null ? 1 : +regMatch[1]; updateByPage(pageNow); }); 

這樣,就完成了。這樣看起來是否會覺得還挺容易的呢?在支援HTML5 history API的瀏覽器中,以上部分就已經做到了帶頁碼記錄的Ajax翻頁。

有待斟酌的相容性問題

根據caniuse上的資料,IE10+及其他主流瀏覽器都支援HTML5 history API。為保證不支援的瀏覽器不報錯,可以加入是否支援HTML5 history API的判斷:

// 參考自 http://modernizr.com/download/#-history 原始碼
var isHistoryApi = !!(window.history && history.pushState);

// ...

if(isHistoryApi){ // ... } 

這樣,一個Ajax翻頁,在支援HTML5 history API的瀏覽器上,將會智慧地儲存當前頁碼資訊,而不支援的瀏覽器仍然可以正常使用,只是不儲存頁碼資訊(就像改進前那樣)。我認為,按照“漸進增強”的思路,這樣就是最好的了,也就是:只使用較少的程式碼優化高階瀏覽器的使用體驗。

如果真的想要在各類瀏覽器裡都表現一致,擁有這樣的記錄功能呢?

這時候推薦使用Benjamin Lupton的History.js,它提供和HTML5 history API近似的api,會在不支援的瀏覽器裡回退到hash形式去處理歷史記錄。儘管為了相容這種hash的回退形式你可能要額外做點事(hash不會發送到伺服器端),但它確實可以讓你做到更廣範圍的相容。

HTML5 history API並不完美

即使只考慮支援HTML5 history API的瀏覽器,它們對HTML5 history API的一些細節處理也會有差異和問題。History.js提供的只針對HTML5瀏覽器的版本,仍然包含了不少處理相容問題的程式碼。

但是,不完美也沒有關係。以我的測試結果,本文所介紹的簡單的寫法,就可以在絕大部分支援HTML5 history API的瀏覽器上正常執行。如果你擔心有哪些瀏覽器會有潛在問題,去測試那個瀏覽器就可以了。你最後用於相容處理的自寫程式碼很可能遠比一個JavaScript庫少得多,畢竟,你也不一定會喜歡額外引入一個JavaScript庫來完成一個功能吧。

一些相關內容

位址列裡的hash曾是過去被廣泛用來記錄頁面狀態的標記,你可以閱讀W3C Blog的這篇文章瞭解它的經歷。

現在可以在不重新整理的狀況下操作瀏覽器位址列和歷史記錄了,那同一站點的普通連結跳轉是否都可以轉變為Ajax來提升使用體驗?是的,而且已經有了pjaxturbolinks這些專門完成這個功能的作品。

不只是翻頁,HTML5 history API將尤其適合用在大量使用Ajax、包含多個檢視的單頁應用。

為一個頁面的每一個狀態都生成一條歷史記錄不一定合適(會讓使用者的歷史記錄變多變亂),酌情使用replaceState()而不是pushState()來控制歷史記錄的數量。

結語

HTML5 history API簡單易學,不多的幾行程式碼就可以做到“狀態記錄”這個小小的改進,如果可以由你選擇“漸進增強”,它還真的可以上線!

--------------------------------------------------

 html4 history api
===================
+ history.length 歷史記錄條數
+ history.go(n) n可為正負數 任意前進或後退n步
+ history.back(); 後退
+ history.forward(); 前進


html5 history api
===================
+ history.pushState(data, title, [url]); *往歷史記錄堆疊頂部新增一條記錄*   
  `data`: onpopstate事件的回撥中作為引數傳入  
  `title`: 頁面標題,目前瀏覽器都會忽略這個引數  
  `url`: 頁面地址 預設為當前url
+ history.replaceState(data, title, [url]); *替換當前歷史記錄,引數同pushState方法*
+ history.state *儲存上述方法的 data資料,不同瀏覽器讀寫許可權不一樣*
+ window.onpopstate *響應pushState或replaceState的呼叫* 

 

跳轉原文