HTML5新特性之History

幾年前,Ajax的興起給互聯網帶來了新的生機,同時也使用戶體驗有了質的飛躍,用戶無需刷新頁面便可獲取新的數據,而頁面也以一種更具備交互性的形式爲用戶展示視圖,能夠說這種變化對互聯網發展的貢獻是前所未有的。javascript

但隨着Ajax大規模應用,愈來愈多的開發人員開始注意到其中存在的問題,由於Ajax的視圖展示是在頁面無刷新狀況下進行的,這也就意味着在用戶作了一系列操做以後,頁面的URL是沒有任何變化的,這些操做所產生的結果天然也就沒法保留,當用戶再次訪問時,想要展示的數據也就沒法重現。css

舉個例如來講,咱們在一個攝影網站瀏覽一個相冊,點擊第一張圖片會動態彈出一個大圖的相框,能夠更好地瀏覽圖片,當看到一個本身喜歡的圖片時,急於分享這張圖片,因而把當前的URL發佈出去了,可是咱們的好友點擊進去後發現,看到的只是原來的那個相冊,並無彈出的大圖相框,更別說咱們要分享的那張圖片了,是否是很失望。也正是由於如此,這個頁面沒法被搜索引擎精確地抓取,SEO沒法作到優化,用戶的可訪問性也大打折扣。html

爲了解決這個問題,開發者開始嘗試使用URL中的hash來提升可訪問性(hash 屬性是一個可讀可寫的字符串,該字符串是 URL 的錨部分(從 # 號開始的部分)),部分瀏覽器開始提供hash相關的事件支持,有些知名站點也和搜索引擎約定使用某種規則來對站點頁面進行抓取,但這畢竟不是一個標準的技術,仍是不可避免的存在不少問題,開發者也期待出現一個標準化的完美解決方案,完全解決這個難題。java

幸運的是,HTML5中的History API給開發者帶了新的但願,它很好的支持了基於URL的頁面無刷新操做,也使得SEO優化獲得完美的解決,毫無疑問,History API將會成爲Web領域將來的標準,愈來愈多的開發者也將會使用它來開發本身的應用程序。jquery

那麼今天,咱們就來說解一下與History相關的技術。瀏覽器

爲了可以更好的闡述這項新技術,咱們選擇使用一個示例程序來開始。以下圖所示,咱們展現三張小圖,點擊任意一張,將會彈出大的預覽圖,而後能夠先後切換預覽圖片:框架

這個程序對於你們來講應該說是比較簡單的,當點擊小圖時,將放置大圖的頁面元素顯示出來,圖片和簡介顯示出與小圖對應的信息便可,切換預覽圖片的操做也是很是容易作到的。ide

這就是傳統的Ajax操做,而這種操做存在一個極其嚴重的缺陷,咱們沒法記錄當前瀏覽的圖片信息,假如後面的小圖有幾百張,當咱們瀏覽到某一張時,以爲很是不錯,想把它發送給朋友,也有可能想本身存下來,這時候咱們會發現,URL中沒有任何這張圖片的信息,複製地址欄也就沒有任何效果了。函數

因此咱們極爲迫切的須要在URL中加入用戶操做所產生的信息,這個時候History API就派上用場了,他提供的方法能夠在頁面不刷新的狀況下對URL進行更新操做。下面就介紹一下主要的兩個方法:優化

history.pushState(stateObj, title, url);

這個方法會往當前會話的歷史棧中放入一條記錄,stateObj是用戶自定義的對象,用於記錄一些有用的數據,title顧名思義就是標題信息,第三個參數是要放入的url信息。例如咱們點擊第二張圖片時能夠執行history.pushState({id: 2}, 'img: 2', '?preview=2');

history.replaceState(stateObj, title, url);

這個方法於pushState相似,惟一不一樣的是,執行這個操做後,瀏覽器會對會話歷史棧內的記錄進行替換而不是添加,這在特定場景下是比較適用的。

咱們這裏就來結合實例來說解一下如何操做歷史記錄。

正如上圖看到的那樣,頁面初始化時展示三張小圖,點擊任意一張小圖時,會彈出預覽圖,頁面的基本框架以下:

 

<!DOCTYPE html>  
<html>  
    <head>  
        <title>History</title>  
        <link rel="stylesheet" type="text/css" href="css/main.css">  
    </head>  
    <body>  
        <ul id="gallery">  
            <li>  
                <img src="img/1.jpg" data-id="1"/>  
            </li>  
            <li>  
                <img src="img/2.jpg" data-id="2"/>  
            </li>  
            <li>  
                <img src="img/3.jpg" data-id="3"/>  
            </li>  
        </ul>  
        <div id="shadow-layer" class="for-dialog"></div>  
        <div id="preview-panel" class="for-dialog">  
            <img id="preview-img"/>  
            <div id="preview-info"></div>  
            <div id="preview-close" class="preview-button">Close</div>  
            <div id="preview-prev" class="preview-button"><</div>  
            <div id="preview-next" class="preview-button">></div>  
        </div>  
    </body>  
    <script type="text/javascript" src="js/jquery.js"></script>  
    <script type="text/javascript" src="js/main.js"></script>  
</html>  

 

而後頁面初始化時,咱們須要爲一些DOM元素綁定事件,就像下面這樣:

//點擊小圖開始預覽模式  
$('#gallery img').click(function() {  
    preview(parseInt($(this).attr('data-id')));  
});  
//關閉預覽視圖  
$('#preview-close').click(function() {  
    $('.for-dialog').hide();  
  
    window.history.back();  
});  
//點擊預覽上一張  
$('#preview-prev').click(function() {  
    switchPrev();  
});  
//點擊預覽下一張  
$('#preview-next').click(function() {  
    switchNext();  
});  

 

能夠看到幾個綁定的事件,點擊小圖會觸發預覽模式;而後在預覽時點擊關閉按鈕,會隱藏預覽框,同時調用了history的back函數,咱們稍後會介紹;最後是兩個切換按鈕的事件,分別會切換到上一張或下一張大圖。咱們注意到,當小圖被點擊時,調用了一個叫preview的函數,這個函數正是咱們重點要講的,代碼以下:

 

function preview(id, isSwitch) {  
  
    $('.for-dialog').show();  
  
    //獲取對應的數據信息  
    var data = getPreviewDataById(id);  
  
    $('#preview-img').attr('src', data.img);  
    $('#preview-info').html(data.info);  
  
    //記錄當前預覽圖片的ID,在先後切換時取用  
    $('#preview-panel').attr('data-prview-id', id);  
      
    var stateObj = {id: id};  
    var operation = isSwitch ? 'replaceState' : 'pushState';  
    //往歷史記錄棧中放入一條記錄或者替換一條記錄  
    window.history[operation](stateObj, 'img: ' + id, '?preview=' + id);  
}  

 

上面的preview函數主要負責幾個重要的工做,首先顯示預覽框,而後根據數據ID獲取數據信息,最後操做歷史記錄對象。在實際開發中,getPreviewDataById函數大可能是以Ajax方式進行的,這個時候只需將後面的邏輯放到回調函數內部便可。在preview函數的最後,咱們往歷史記錄裏面放入或替換一條包含ID信息的對象,同時改變URL,放入仍是替換,取決因而不是先後切換預覽圖,咱們稍後會詳細解釋一下。注意,這個時候雖然URL改變了,但頁面並不會刷新。下面咱們就來對比一下點擊先後的效果,以點擊第二張小圖爲例:

 

能夠明顯看到,地址欄已經變化了,這其實就是咱們調用pushState函數的結果:

window.history.pushState({id: 2}, 'img: 2', '?preview=2');  

 

須要額外注意的一點是,第三個參數是新的URL地址,根據官方文檔介紹,它能夠是相對地址,也能夠是絕對地址,因此咱們也能夠選擇使用絕對地址,就像下面這樣,效果是同樣的:

 

window.history.pushState({id: 2}, 'img: 2','http://localhost/history/?preview=2');  

 

 

在上面的preview函數參數列表中,存在一個isSwitch參數,若是函數被調用時指定這個參數,則會調用replaceState,而不是pushState,這是爲何呢,下面就來解釋一下。

還記得咱們咱們爲預覽框右上角關閉按鈕綁定的事件嗎,除了隱藏預覽圖,還調用了history的back函數,其實咱們是要利用這個函數從歷史棧中彈出以前放入的歷史記錄,進而將URL恢復到預覽前的狀態。那麼,若是咱們在預覽的時候頻繁的切換到下一張或者上一張,歷史記錄棧中就會存在不少記錄,要回到初始狀態顯然不易,咱們也沒有必要保留這麼多的歷史記錄,因此當用戶先後切換預覽圖時,只須要調用replaceState替換當前歷史棧中那條記錄便可,假如咱們點擊右邊的切換按鈕,應該是這樣的:

 

window.history.replaceState({id: 3}, 'img: 3', '?preview=3');  

 

 

爲了調用replaceState函數,咱們須要在執行preview函數時,傳入一個isSwitch參數,咱們來看看是如何調用的:

 

//預覽下一張  
function switchNext() {  
    //根據當前ID計算下一個ID  
    var currId = parseInt($('#preview-panel').attr('data-prview-id'));  
    currId++;  
    currId > 3 && (currId = 1);  
  
    preview(currId, true);  
}  

 

下面就以兩個示意圖來說解一下切換先後的棧結構:

 

這樣咱們就能保證歷史棧中只有一條記錄,當關閉預覽框結束預覽時,可當即恢復到以前的視圖。

而向前切換也是同樣的,代碼以下:

 

//預覽上一張  
function switchPrev() {  
    //根據當前ID計算前一個ID  
    var currId = parseInt($('#preview-panel').attr('data-prview-id'));  
    currId--;  
    currId < 1 && (currId = 3);  
  
    preview(currId, true);  
}  

 

到這裏,我想你們都已經清楚如何操做歷史棧來改變URL,那麼咱們不只要問,若是使用當前的URL從新訪問站點,如何還原關閉以前的視圖呢,其實這個就屬於基本的操做了,只須要在onload函數時獲得相應的參數,而後調用preview函數便可,如代碼所示:

 

//從URL中獲取到當前預覽圖片的ID  
function getCurrPreviewId() {  
    var search = location.search;  
    if (!search) return -1;  
  
    var params = {};  
  
    var keyValues = search.substring(1).split('&');  
    keyValues.forEach(function(item) {  
        var parts = item.split('=');  
        params[parts[0]] = parts[1];  
    });  
    if (!params['preview']) return -1;  
  
    return params['preview'];  
}  
  
//當頁面加載時,若是URL中有預覽信息,取出而後  
window.onload = function() {  
    var id = getCurrPreviewId();  
    if (id < 1 || id > 3) return;  
  
    preview(id);  
};  

 

到這裏其實咱們已經將整個過程講解完成了,但還有一個重要的事件咱們尚未涉及到,那就是window下面的onpopstate事件,當用戶點擊後退按鈕或前進按鈕時,或者當咱們的程序執行history.back(),history.forward(),history.go()時,都會觸發這個事件,它可以捕獲這些操做完成以後,當前歷史棧中的狀態,咱們能夠利用這些信息,作進一步的操做。那麼咱們就來作個試驗,看看onpopstate事件是如何被觸發的:

 

window.onpopstate = function(e) {  
    console.log('e.state:', e.state);  
};  

 

在這個事件函數內,咱們會打印事件對象中的state對象,其實就是咱們執行pushState函數時放進去的對象,下面咱們在控制檯作下面幾個操做看看效果:

 

如圖所示,咱們放入兩條記錄,當後退或前進的時候,都會執行到onpopstate事件函數,而且可以獲取到當前的狀態對象信息,若是咱們的圖片預覽功能須要記錄每一步的操做,那麼咱們就能夠在onpopstate事件函數中獲取到當前須要展示的圖片ID,而後直接調用preview函數便可。

最後,須要注意的是,當前有些瀏覽器還不能很好的支持History API,因此咱們最好判斷一下瀏覽器的支持狀況:

 

var isHistoryStateSupported = 'pushState' in window.history;  

 

講到這裏,關於History的相關知識及應用基本上就告一段落了,但願你們可以細細體會它的應用場景,並加以練習,最後但願你們可以很好地掌握並運用到將來的項目中去。

相關文章
相關標籤/搜索