Ajax保留瀏覽器歷史的兩種解決方案(Hash&Pjax)

liyichaodoom3發表於2011-06-28

  總是在github down點東西,github整個介面做的不錯,體驗也很好~對於其中的原始碼滑動的特效最為喜歡了~剛開始以為這個只是普通的ajax請求效果,但是發現這個特效能夠導致瀏覽器位址列跟隨變化,並且再點選前進後退按鈕後又可以將程式碼滑回滑出~~於是乎就來研究下吧~

  一、通過錨點Hash實現:

  在這方面其實國內很早就有做了,比如淘寶畫報,通過的是在位址列後面加#錨點實現的,瀏覽器是可以識別錨點為單位的歷史記錄的。但不是說頁面本身有這個錨點,錨點的Hash只是起到一個引導瀏覽器將這次的記錄推入歷史記錄棧頂的作用。

  來做一個小小的demo:

    <style type="text/css">
        #tab1_header,#tab2_header{
            cursor:pointer;
            border:1px solid;
            width:50px;
        }
        #tab1,#tab2{
            width:90%;
            height:200px;
            border:1px solid;
        }
    </style>
    <div id="tab_header">
    	<span id="tab1_header">Tab1</span>
    	<span id="tab2_header">Tab2</span>
    </div>
    <div id="tab1">1</div>
    <div id="tab2">2</div>

  一個很簡單的Tab切換如果一般情況下就直接:

$("#tab1_header").click(function() {
            $("#tab2").hide();
            $("#tab1").show();
});
$("#tab2_header").click(function() {
            $("#tab1").hide();
            $("#tab2").show();
});

  但假如點選到tab2時想通過後退按鈕退到tab1時就不行了,假如重新整理的話瀏覽器的行為完全不是出於使用者的想法,這樣的話,我們可以加入#錨點來模擬新頁面,為什麼要說模擬呢,假如直接通過js改變window.location瀏覽器會重新載入頁面,但加#就不會重新載入並且能儲存在歷史中。JS通過window.location.hash來控制URL後面的錨點#。

  我們把程式碼改為這樣:

$(function(){
			showTab();
			$(window).bind('hashchange', function(e){
				showTab();
			});
			$("#tab1_header").click(showTab1);
			$("#tab2_header").click(showTab2);
		});

        function showTab() {
            if (window.location.hash == "#tab2"){
				showTab2();
			} else {
				showTab1();
			}
        }
        function showTab1() {
            $("#tab2").hide();
            $("#tab1").show();
            window.location.hash = "#tab1";
        };
        function showTab2() {
            $("#tab1").hide();
            $("#tab2").show();
            window.location.hash = "#tab2";
        };

  加上window.location.hash = "#tab1"這一段程式碼就行了,在點選tab後,位址列後面就會加上#tab1,點選tab2後就會改成#tab2,當瀏覽器檢測到url變化時就會觸發hashchange這一事件,就是使用者在點選後退時能夠得到的事件就能夠通過window.location.hash進行判斷並進行ajax操作了,但是haschange這個事件並不是每個瀏覽器都有的,只有現代高階瀏覽器才有,所以在低階的瀏覽器中需要用輪詢來檢測URL是否在變化,這個這裡就不具體說了。

  二、通過HTML5加強型的History物件實現(類Pjax)

  可以通過window.history.pushState這個方法無重新整理的更新瀏覽器位址列,這個方法在更新位址列的同時將地址壓入歷史記錄堆疊裡,而要取出這個棧頂頁面則可以用popstate這個事件來捕獲~

  來模擬一下github的環境,github中每個url是對應一個完整的實際頁面的,所以在ajax請求頁面時需要非同步獲取target頁面中指定id容器中的內容:

  比如有這樣兩個頁面:

  index.html

<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=gbk">
        <title>index</title>
    </head>
    <body>
    	<script>document.write(new Date());</script>
    	<div id="cn">
        	<a href="second.html">載入前</a>
        </div>
    </body>
</html>

  second.html  

<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=gbk">
        <title>second</title>
    </head>
    <body>
    	<script>document.write(new Date());</script>
        <div id="cn">
        	<a href="index.html">載入後</a>
        </div>
    </body>
</html>

  假如用同步的http請求開啟的話完全是兩個頁面,兩個頁面加入很多地方一樣的話我們完全可以用這種方法來實現ajax請求變更DOM,我在這裡加了<script>document.write(new Date());</script>語句通過它的變化能得知是否取自兩個http請求,局

  部的ajax請求是不會改變這個時間顯示的。  

$(function() {
    var state = {
        title: "index",
        url: "index.html"
    };
    $("#cn").click(function() {
        window.history.pushState(state, "index", "second.html");
        var $self = $(this);
        $.ajax({
            url: "second.html",
            dataType: "html",
            complete: function(jqXHR, status, responseText) {
                responseText = jqXHR.responseText;
                if (jqXHR.isResolved()) {
                    jqXHR.done(function(r) {
                        responseText = r;
                    });
                    $self.html($("<div>").append(responseText.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")).find("#cn"));
                }
            }
        });
        document.title = "second";
        return false;
    });
    $(window).bind('popstate', function(e) {
        var st = e.state;
        //$("#cn").load(st.url + " #cn");
        $.ajax({
            url: "index.html",
            dataType: "html",
            complete: function(jqXHR, status, responseText) {
                responseText = jqXHR.responseText;
                if (jqXHR.isResolved()) {
                    jqXHR.done(function(r) {
                        responseText = r;
                    });
                    $("#cn").html($("<div>").append(responseText.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")).find("#cn"));
                }
            }
        });
        document.title = e.state.title;
    });
});

  上面語句中當#cn元素被點選時將state通過pushState方法壓入歷史記錄棧,並在第三個引數中將瀏覽器URL框中指向second頁面,並通過ajax將second頁面非同步載入,將相應的部分加入容器中,這樣就實現了非同步載入並改變位址列url了,同樣使用者點選後退時,觸發popstate,剛才pushState方法中的第一個引數state便是這裡傳入的形參e中的state屬性,通過var st = e.state取出供開發使用。同時載入index頁面中對應內容。時間有限這個js沒有進行重構,直接寫$.ajax了,其實假如不需要任何特效單純的非同步載入在jQ中可以直接用$("#cn").load(st.url + " #cn");將請求的html對應的#cn放到本頁面的#cn容器中,但加入要更加炫的特效的話就要直接操作ajax傳回的資料了。

$("#cn").html($("<div>").append(responseText.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")).find("#cn"));

  先建立一個div容器在將經過script過濾過的程式碼裝入這個容器在通過find方法找到裡面對應的選擇器容器插入本身的頁面中,這裡可以不用html來填充,可以根據自己的專案需要用slideUp,show什麼的特效進行內容顯示~~

  另外這裡要推薦一個jQuery元件叫pjax(https://github.com/defunkt/jquery-pjax),比較牛叉的一個元件,非同步的部分load進來另外一個頁面對應容器中的內容,實現的機理和我上面的第二種方案一致。pushState + ajax = pjax 感覺這個應用會熱起來的。

  稍微總結下,兩種方案其實對於想IE6或者FF3.6等比較低階的瀏覽器支援不是很好,前者若要相容低端瀏覽器要用輪詢來監聽瀏覽器位址列行為,而後者的話是完全的HTML5應用,對於非HTML5瀏覽器只能做判斷跳轉了。

  如pjax最後的一段無奈的相容處理:  

$.support.pjax = window.history && window.history.pushState


// Fall back to normalcy for older browsers.
if ( !$.support.pjax ) {
  $.pjax = function( options ) {
    window.location = $.isFunction(options.url) ? options.url() : options.url
  }
  $.fn.pjax = function() { return this }
}

相關文章