信息比較豐富的網(wǎng)站通常會以分頁顯示,在點“下一頁”時,很多網(wǎng)站都采用了動態(tài)請求的方式,避免頁面刷新。雖然大家都是ajax,但是從一些小的細節(jié)還是 可以區(qū)分優(yōu)劣。一個小的細節(jié)是能否支持瀏覽器“后退”和“前進“鍵。本文討論兩種方法,讓瀏覽器可以后退和前進,或者說讓ajax就像重定向到新頁面一樣 擁有能夠返回到上一頁或者前進到下一頁。
數(shù)據(jù)實現(xiàn)分頁顯示,最簡單的做法是在網(wǎng)址后面加多個page的當數(shù),點“下一頁”時,讓網(wǎng)頁重定向到page+1的新地址。例如新浪的新聞網(wǎng)就 是這么做的,通過改變網(wǎng)址實現(xiàn):index_1、index_2、index_3……。但是如果這個列表并不是頁面的主體部分,或者頁面的其它部分有很多 圖片等豐富元素,例如導航是一個很大的slider,再使用這樣的方式,整個頁面會閃爍得厲害,并且很多資源得重新加載。所以使用ajax請求,動態(tài)改變 DOM。
但是普通的動態(tài)的請求不會使網(wǎng)址發(fā)生變化,用戶點了下一頁,或者點了第幾頁,想要返回到上一個頁面時,可能會去點瀏覽器的返回鍵,這樣就導致返回的時候不是返回到原先查看的頁面了,而是上一個網(wǎng)址了。例如央視的新聞網(wǎng)就是這樣的。下面從ajax請求開始說起,以一個完整的案例進行分析。
做了一個demo
首先,寫一個請求:
//當前第幾頁 var pageIndex = 0; //請求函數(shù) function makeRequest(pageIndex){ var request = new XMLHttpRequest(); request.onreadystatechange = stateChange; //請求傳兩個參數(shù),一個是當前第幾頁,另一個是每頁的數(shù)據(jù)條數(shù) request.open("GET", "/getBook?page=" + pageIndex + "limit=4", true); request.send(null); function stateChange(){ //狀態(tài)碼為4,表示loaded,請求完成 if(request.readyState !== 4 ){ return; } //請求成功 if(request.status >= 200 request.status 300 || request.status === 304){ var books = JSON.parse(request.responseText); renderPage(books); } } }
拿到數(shù)據(jù)后進行渲染:
function renderPage(books){ var bookHtml = "table>" + " tr>" + " th>書名/th>" + " th>作者/th>" + " th>版本/th>" + " /tr>"; for(var i in books){ bookHtml += "tr>" + " td>" + books[i].book_name + "/td>" + " td>" + books[i].author + "/td>" + " td>" + books[i].edition + "/td>" + "/tr>"; } bookHtml += "/table>"; bookHtml += "button>上一頁/button>" + "button onclick='nextPage();'>下一頁/button>"; var section = document.createElement("section"); section.innerHtml = bookHtml; document.getElementById("book").appendChild(section); }
這樣一個基本的ajax請求就搭起來了,然后再響應“下一頁”按鈕:
function nextPage(){ //將頁面的index加1 pageIndex++; //重新發(fā)請求和頁面加載 makeRequest(pageIndex); }
到此,如果不做任何處理的話,就不能夠發(fā)揮瀏覽器返回、前進按鈕的作用。
如果能夠檢測用戶點了后退、前進按鈕的話,就可以做些文章。h5就是增加了這么一個事件window.onpopstate,當用戶點擊那兩個按鈕就會觸 發(fā)這個事件。但是光檢測到這個事件是不夠的,還得能夠傳些參數(shù),也就是說返回到之前那個頁面的時候得知道那個頁面的pageIndex。通過 history的pushState方法可以達到這個目的,pushState(pageIndex)將當前頁的pageIndex存起來,再返回到這個 頁面時獲取到這個pageIndex。pushState的參數(shù)如下:
其中state為一個object{},用來存放當前頁面的數(shù)據(jù),title標題沒有多大的作用,url為當前頁面的url,一旦更改了這個url,瀏覽器地址欄的地址也會跟著變化。
于是,在請求下一頁數(shù)據(jù)的nextPage函數(shù)里面,加多一步操作:
function nextPage(){ pageIndex++; makeRequest(pageIndex); //存放當前頁面的數(shù)據(jù) window.history.pushState({page: pageIndex}, null, window.location.href); }
然后監(jiān)聽popstate事件:
//如果用戶點擊返回或者前進按鈕 window.addEventListener("popstate", function(event){ var page = 0; //由于第一頁沒有pushState,所以返回到第一頁的時候是沒有數(shù)據(jù)的,因此得做下判斷 if(event.state !== null){ page = event.state.page; } makeRequest(page); pageIndex = page; });
state數(shù)據(jù)通過event傳進來,這樣就可以得到pageIndex。
但是,這樣實現(xiàn)還有問題,在第二頁的時候如果刷新頁面的話,會發(fā)生錯亂,如下所示:首先點下一頁到第二頁,然后刷新頁面,出現(xiàn)第一頁,再點下一頁,出現(xiàn)第二頁,點返回時出現(xiàn)問題,顯示還是第二頁,不是期望的第一頁,直到再次點返回時才是第一頁:
從右邊的工具欄可以發(fā)現(xiàn),點第一次返回的時候獲取到的pageIndex仍然是1。對于這種情況,需要分析history模型,如下所示:
可以理解為對history的操作,瀏覽器有一個隊列,用來存放訪問的記錄,包括每個訪問的網(wǎng)址還有state數(shù)據(jù)。一開始,隊列的首指針指向page = 0的位置,點下一頁時,執(zhí)行了pushState,在這個隊列插入了一個元素,同時通過pushState操作記錄了這個元素的url和state數(shù)據(jù)。 在這里可以看出,pushState的操作最重要的作用還是給history隊列插入元素,這樣瀏覽器的后退按鈕才不是置灰的狀態(tài),其次才是上面說的存放 數(shù)據(jù)。點后退的時候,隊首指針后退一步指向page = 0的位置,點前進時又前進指向page = 1的位置。
如果在page = 1的位置刷新頁面,模型是這個樣子的:
在第2步刷新的時候,頁面的pageIndex又恢復成默認值0,所以page = 0,顯示第一頁數(shù)據(jù),但是history所用的隊列并沒有改變。然后再點下一頁時,又給這個隊列push了一個元素,這個隊列就有兩個pageIndex 為1的元素,所以必須得兩次返回才能回到page = 0的位置,也就是上面說的錯亂的情況。
根據(jù)上面的分析,這樣的實現(xiàn)是有問題的,一但用戶不是在page = 0的位置刷新頁面,就會出現(xiàn)需要點多次返回按鈕才能夠回到原先的頁面。
所以得在刷新的時候,把當前頁的state數(shù)據(jù)更新一下,用replaceState,替換隊列隊首指針的數(shù)據(jù),也就是當前頁的數(shù)據(jù)。方法是頁面初始化時replace一下:
window.history.replaceState({page: pageIndex /*此處為0*/}, null, window.location.href);
這樣模型就變成:
但其實用戶刷新的時候更希望的是還是顯示當前頁,而不是回到第一頁。一個解決辦法是用當前頁的window.history.state數(shù)據(jù),這個屬性瀏覽器支持得比較晚。在頁面初始化時設置pageIndex時就從history.state取:
var pageIndex = window.history.state === null ? 0 : window.history.state.page;
safari里面的history.state是最近執(zhí)行pushState傳入的數(shù)據(jù),因此這個辦法在chrome/firefox里面行得通,但是safari行不通。
第二種辦法是借助h5的localStorage存放當前頁數(shù):
//頁面初始化,取當前第幾頁先從localStorage取 var pageIndex = window.localStorage.pageIndex || 0; function nextPage(){ //將頁面的index加1,同時存放在localStorage window.localStorage.pageIndex = ++pageIndex; //重新發(fā)請求和頁面加載 makeRequest(pageIndex); window.history.pushState({page: pageIndex}, null, window.location.href); } window.addEventListener("popstate", function(event){ var page = 0; if(event.state !== null){ page = event.state.page; } makeRequest(page); //點擊返回或前進時,需要將page放到localStorage window.localStorage.pageIndex = page; });
將頁面中所有改變pageIndex的地方,同時放到localStorage。這樣刷新頁面的時候就可以取到當前頁的pageIndex。
上面的方法都是將pageIndex放到了state參數(shù)里,還有一種方法是把它放到第三個參數(shù)url里,也就是說通過改變當前頁網(wǎng)址的辦法。pageIndex從網(wǎng)址里面?。?/p>
//當前第幾頁 var pageIndex = window.location.search.replace("?page=", "") || ; function nextPage(){ //將頁面的index加 ++pageIndex; //重新發(fā)請求和頁面加載 makeRequest(pageIndex); window.history.pushState(null, null, "?page=" + pageIndex); }
注意,一旦執(zhí)行了第8行的pushState,當前網(wǎng)址的地址就會發(fā)生變化。
有一點需要注意的是,window.history.length雖然返回是的當前隊列的元素個數(shù),但不代表history本身就是那個隊列,通過不同瀏覽器的對history[i]的輸出:
可以看到history是一個數(shù)組,它的作用是讓用戶拿到history.length,當前的長度,但是填充的內容是不確定的。
除了使用history之外,還有借助hash的方法,網(wǎng)易新聞就是使用了這樣的方法:
//當前第幾頁 var pageIndex = window.location.hash.replace("#page=", "") || ; function nextPage(){ makeRequest(pageIndex); window.location.hash = "#page=" + pageIndex; } window.addEventListener("hashchange", function(){ var page = window.location.hash.replace("#page=", "") || ; makeRequest(page); });
關于支持性,參考caniuse網(wǎng)站:history IE10及以上支持,hashchange的支持性較好,IE8及以上都支持。
雖然hashchange的支持性較好,但是history的優(yōu)點是可以傳數(shù)據(jù)。對一些復雜的應用可能會有很大的發(fā)揮作用,同時history支持back/go操作。
以上本文關于h5的history改善ajax列表請求體驗,希望大家喜歡。