效果:
眾所周知:ajax可以實現頁面的局部刷新,可以做到非常奈斯的數據加載效果,給用戶帶來非常良好的體驗,但是ajax的除了會曝露一些不太安全的服務器信息之外,最蛋疼的就是不能在瀏覽器的歷史會話中保留記錄。就是當你點開一個頁面,ajax各種數據加載各種歡樂啊,例如一個列表頁面,異步加載來翻頁。結果用戶一不小心刷新了頁面,那么頁碼就得重新開始計算,一旦用戶改變了會話狀態(瀏覽器的前進、后退、刷新),那么ajax就會丟失相關的數據。
最近在網上瀏覽各大網站無意中發現了ajax異步刷新但是地址欄改變的效果。
例如google doodles,大家可以點擊下面的鏈接后翻頁查看圖片,就可以發現頁面ajax刷新地址欄也同步改變了,飛機票在這里,請戳:
http://www.google.com/doodles/grandfathers-day-2014
優點如下:
- 卓越的用戶體驗
- 對搜索引擎更加友好
- 更多敬請補充...
實現方案(寫入會話狀態):
對這種效果抱着好奇的心態就去查閱了一下資料。
在Html5中為window.history
對象引入了pushState和replaceState方法,對
window.history
對象還不太了解的童鞋請自行查閱相關資料。
示例:
//格式約定 history.pushState(data, title[, url]); /// <summary> /// pushState方法調用示例,replaceState方法同樣的參數格式,
/// 本方法負責將自定義數據寫入瀏覽器會話歷史
/// linkFly原創,引用請注明出處,謝謝 /// </summary> /// <param name="data" type="object"> /// 需要進行保存(在歷史會話)的數據,該數據可以在下一次會話中讀取出來 /// </param> /// <param name="title" type="String"> /// 寫入歷史會話的標題,經過測試暫時沒有發現用處,不會對當前文檔標題產生影響,可以傳入空字符串 /// </param> /// <param name="url" type="String"> /// 要寫入瀏覽器歷史會話的Url【注意不允許跨域】 /// </param> /// <returns type="void" /> window.history.pushState(
{pageIndex:1,keywords:'善了個哉'},
document.title,
window.location.pathname+'?pageIndex=1'
);
原理:
該方法隸屬Html5,是為了解決ajax方法不能“回到過去”的問題。window.history
負責管理瀏覽器會話歷史,而pushState則在瀏覽器歷史中添加一條會話歷史(replaceState則是替換一條歷史)作為當前會話狀態,而在用戶刷新了頁面之后進入這條會話,這時候我們只需要把會話的數據讀取出來就行了。
對每個參數進行特別說明一下:
- Data:可以任意存放一組數據,這組數據存放在該歷史對話中,下次進入該對話則可以通過讀取該數據來進行狀態保持,例如可以存放頁碼信息,但是注意值不能引用對象,會報ObjectCloneError(對象克隆異常),例如當前頁面的Element、$("#testId")這種引用了依賴當前頁面的數據,Data數據要保持獨立,值不允許為引用類型的對象。
- Title:參閱的大部分文檔對其都沒有說明,只是簡單的描述了文檔標題。應該是在讀取會話數據的時候自動對應到頁面標題上,但是經過測試並不會對頁面標題有任何的影響,如果是為了實現自動對應標題的話,功能就有點雞肋(完全可以用Data),一般可以傳入:document.title或''(空字符串)
- Url:就是要寫入歷史會話的url,假定我們已經在歷史記錄中寫入了Url,瀏覽器進行前進操作,跳轉到了別的頁面,然后再返回,進入的就是這個url。不允許跨域!本着面向對象的思想,一般可以通過window.location.pathname獲取當前url地址,然后通過字符串拼接后面的參數傳入就可以生成穩妥的url了。
讀取會話歷史:
在輕松寫入了會話歷史之后,我們還需要將它讀出來才行。這個讀取的切入點,嗯,查閱了資料說的都是通過onpopstate事件,實際上在這個事件上非常的蛋疼,我查閱的多數的文章都告訴說捕捉這個事件即可,代碼如下:
本示例不推薦使用:
//本代碼僅作參考 //環境 Firefox 25.0.1級以下版本不推薦使用本代碼(高版本尚未測試) window.addEventListener('popstate', function(e) {
/// <summary>
/// 在頁面初始化加載完成中添加該事件,則可以監聽到onpopstate事件,而瀏覽器進行前進、后退、刷新操作都會觸發本事件
/// linkFly原創,引用請注明出處,謝謝 /// </summary>/// <returns type="void" /> if (e.state) { //e.state就是pushState中保存的Data,我們只需要將相應的數據讀取下來即可 } }); // 傳聞可以直接使用history.state來獲取當前對應的state數據,筆者尚未測試,有興趣的可以自行研究下,注意主要測試Firefox
注意,以上代碼在Firefox下存在問題。
在這些資料中都只是草草的告訴說onpopstate事件可以做到讀取數據,但在Firefox下,頁面加載中根本不會觸發onpopstate事件。
大家注意中間一大段的最后一句:
Chrome and Safari always emit a popstate
event on page load, but Firefox doesn't.
翻譯過來就是Chrome和Safari都會在頁面加載中觸發該事件,但是Firefox不會。
所以這時候有兩種處理方案:
- 在頁面加載中手動觸發該事件
- 通過解析url來實現,注意如果希望通過url來獲取數據的話,那么之前是pushState主要需要保存的數據並非是data,而是url。這個概念需要清晰。
在頁面加載中手動觸發該事件代碼如下:
$(function(){ //通過jQuery.trigger()方法觸發 //或者自己手寫js觸發,具體代碼這里就不貼了... $(window).trigger("hashchange"); });
通過解析url代碼如下:
function getUrlParameter(fieldName) { /// <summary> /// 1: 獲取地址欄參數方法 /// - getUrlParameter(fieldName) - 在當前Url中查詢指定的參數,返回查詢得到的值,當不支持pushState或沒有查詢到參數的時候返回空字符串 /// </summary> /// <param name="fieldName" type="String"> /// 要查詢的字符串 /// </param> /// <returns type="String" />
if (window.history.pushState) { var urlString = document.location.search; if (urlString != null) { var typeQu = fieldName + "="; var urlEnd = urlString.indexOf(typeQu); if (urlEnd != -1) { var paramsUrl = urlString.substring(urlEnd + typeQu.length); var isEnd = paramsUrl.indexOf('&'); if (isEnd != -1) { return paramsUrl.substring(0, isEnd); } else { return paramsUrl; } } else { return ""; } } else { return ""; } } else { return ''; } } //調用方法:getUrlParameter('要查詢的參數')
總結:
表達能力實在有限,快速總結一下。我個人采用的是url的方法來獲取參數的,因為覺得這樣更加穩妥,畢竟對於onpopstate中e.state琢磨的還是很透徹,而url從某種方式上來更加的合理與穩妥一點。具體還需要根據實際情況來處理,采用url的方式需要服務器上對相應的url進行一番處理。
- window.history.pushState()方法中參數Data里面的值不允許存在和頁面相關引用對象,可以這樣{ pageIndex : 1 },但是不可以這樣{ pageDom : document.getElementById('testId') }
- window.history.pushState()方法中參數url不允許跨域。
- 目前在Firefox下onpopstate事件不會在page load中加載執行。
- 如果后台需要區別是ajax還是經過push歷史的ajax,可以在pushState中的ajax里面發送特殊的請求頭,后台接收到該特殊的請求頭信息后進行特殊處理。
- 還有沒補充的例如瀏覽器兼容性神馬的請拉到文章最下面。
其他:
提供一份自己寫的一份相應的js,直接copy使用即可。
//historyState對象,提供push歷史數據和獲取歷史數據方法。 //linkFly原創,引用請注明出處,謝謝 var historyState = { checkCanPush: function () { /// <summary> /// 檢測瀏覽器是否支持pushState方法 /// </summary> /// <returns type="Boolean" /> if (window.history.pushState) return true; return false; }, pushState: function (data, url) { /// <summary> /// 狀態保持方法(需要高版本瀏覽器支持),當canPush為true的時候表示瀏覽器可以進行push狀態,則進行狀態push並返回是否成功 /// 1.1 - pushState(data,url) 將指定的data,和url push到瀏覽器會話歷史進行狀態保持【注意Url不允許跨域】 /// </summary> /// <param name="url" type="String"> /// 需要寫入瀏覽器會話歷史的url /// </param> /// <returns type="Boolean" /> if (historyState.checkCanPush()) { //注意data雖然可以保存數據,但是不能保存仍然引用着當前頁面元素的對象,例如$("DOM")這樣一個對象,就會出現ObjectCloneError window.history.pushState(data, document.title, url); return true; } return false; }, getUrlParameter: function (fieldName) { /// <summary> /// 1: 獲取地址欄參數方法 /// - getUrlParameter(fieldName) - 在當前Url中查詢指定的參數,返回查詢得到的值,當不支持pushState或沒有查詢到參數的時候返回空字符串 /// </summary> /// <param name="fieldName" type="String"> /// 要查詢的字符串 /// </param> /// <returns type="String" /> if (historyState.checkCanPush()) { var urlString = document.location.search; if (urlString != null) { var typeQu = fieldName + "="; var urlEnd = urlString.indexOf(typeQu); if (urlEnd != -1) { var paramsUrl = urlString.substring(urlEnd + typeQu.length); var isEnd = paramsUrl.indexOf('&'); if (isEnd != -1) { return paramsUrl.substring(0, isEnd); } else { return paramsUrl; } } else { return ""; } } else { return ""; } } else { return ''; } } } //historyState對象調用示例 window.onload = function () { //寫入歷史會話 historyState.pushState({ pageIndex: 1 }, window.location.pathname + '?pageIndex=1'); //獲取歷史會話的數據(獲取url的參數數據) var pageIndex = historyState.getUrlParameter('pageIndex'); //進行數據還原操作... }
相關資料: