最近在做一個移動端項目,發現移動端某些返回和PC端是有差異的, 比如ios中返回按鈕是直接使用緩存的, 不會執行任何js代碼, 這個問題很蛋疼, 例如, 在提交的時候將按鈕設置為loading狀態, 如果在提交成功后沒有對按鈕進行處理, 那么返回后按鈕依然是loading狀態, 這種體驗很差, 如下圖:

此問題是由於某些瀏覽器在back的時候是直接使用的之前的視圖,頁面沒有進行重新加載而導致的,在網上找了些資料, 發現這是H5的一些新特性Back-Forward Cache(簡稱bfcache) ,普通瀏覽器在back時,如果不是指定Cache-Control、Expires等方法強制停用Cache時,那么一般情況下瀏覽器大多數都會直接讀取本地的緩存, 減少請求和網絡傳輸的成本, 增加瀏覽的順從度, 但Cache僅限於靜態文件, 瀏覽器還是得重新加載html, 重新執行腳本,渲染DOM, 而bfcache則不同, 是直接讀取緩存里面的html,節省了重新請求頁面的時間, 既然是讀取緩存的html頁面, 那么執行頁面的onload事件進行初始化, 會影響原本因用戶操作而改變的狀態, 所以瀏覽器在back時是不會觸發onload事件.
這個時候就會產生上面的問題, 有些業務在返回時是需要重新加載的, 於是H5新增了兩個事件onpageshow和onpagehide, 分別是進入網頁和離開的時候觸發, 即使是用瀏覽器的前進/后退也會觸發這兩個事件.
1 <!DOCTYPE html> 2 3 <html> 4 <head> 5 <title>Page Events</title> 6 <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.js"></script> 7 <script> 8 function dispLog(msg) { 9 var d = new Date(); 10 $("<li />").text(d.toISOString().substr(14, 9) + " " + msg) 11 .appendTo("#dvDisp"); 12 13 } 14 $(window).load(function () { 15 dispLog("Load Event"); 16 }).ready(function () { 17 dispLog("Ready Event"); 18 $("#btnSetColor").click(function () { 19 $("a").css("color", "red"); 20 }); 21 }).bind("pageshow", function () { 22 dispLog("PageShow Event"); 23 }).bind("pagehide", function () { 24 dispLog("PageHide Event"); 25 }); 26 </script> 27 </head> 28 <body> 29 <a href="test1.html">前往其它頁面</a> 30 <input type="button" id="btnSetColor" value="變色" /> 31 <ul id="dvDisp"></ul> 32 </body> 33 </html>
頁面很簡單, 綁定onload, ready,onpageshow,onpagehide四個事件, 觸發事件相應的文本會顯示在頁面上, 另外這里有個連接可跳轉到其它網頁,便於測試back, button事件會改變連接的顏色, 便於back時檢查顏色是否保留,判斷是否有bfcache.
測試步驟打開test.html, 點擊變色按鈕, 再點擊"前往其它頁面", 然后在test1.html點擊back按鈕回到test.html. 在幾次測試后, 大致的測試結果如下:
IE9
打開頁面或則back時都會觸發Ready/Load事件, 紅色未保留, 無bfcache.
IE10 (Windows 8 Release Preview)
打開頁面或則back時都會觸發Ready/Load事件, 紅色未保留, 無bfcache.
Chrome 21.0.1180.6
打開頁面或則back時都會觸發Ready/Load/PageShow事件, 紅色未保留, 無bfcache.
Firefox 15.0
打開頁面或則back時都會觸發Ready/Load/PageShow事件,點擊[前往其它網頁]會觸發PageHide, [back]時會觸發PageShow, 紅色被保留, 有bfcache.
Safari 5.1.5
打開頁面或則back時都會觸發Ready/Load/PageShow事件,點擊[前往其它網頁]會觸發PageHide, [back]時會觸發PageShow, 紅色被保留, 有bfcache.
Safari on iPad (iOS 5.1.1)
打開頁面或則back時都會觸發Ready/Load/PageShow事件,點擊[前往其它網頁]會觸發PageHide, [back]時會觸發PageShow, 紅色被保留, 有bfcache.
Opera 12.00
打開頁面或則back時都會觸發Ready/Load事件, [back]時會觸發PageShow, 紅色被保留, 有bfcache但不會觸發PageShow事件.
總結: Firefox和Safari會bfcache, back時不會觸發load, ready事件, 只會觸發onpageshow, 而chrome雖然支持onpageshow, 但是back時一樣都會觸發load,ready事件, opera最操蛋, back時會bfcache,但是不觸發onpageshow事件.
回到上面的問題, 如何解決bfcache時ready在back時不執行的問題呢?
起初是想新增一個$.pageshow(), 若瀏覽器支持, 將業務代碼放在onpageshow事件里面處理, 否則用ready處理, 如下:
1 $.pageshow = function (fn) { 2 if (typeof window.onpageshow == "undefined") 3 $(document).ready(fn); 4 else 5 $(window).bind("pageshow", fn); 6 }; 7 $.pageshow(function () { 8 alert("Page Show"); 9 alert(typeof window.onpageshow == "undefined") 10 });
很艹蛋啊, 這個方法只能解決Firefox、Safaer上的問題, 但是在Opera上就沒什么效果.
還好在MDC的文檔上找到一點思路, Firefox在某些條件下禁用bfcache:
There are instances in which Firefox doesn’t cache pages. Below are some common programmatic reasons that a page is not cached: the page uses an unload or beforeunload handler; the page sets "cache-control: no-store". the site is HTTPS and page sets at least one of: "Cache-Control: no-cache" "Pragma: no-cache" with "Expires: 0" or "Expires" with a date value in the past relative to the value of the "Date" header (unless "Cache-Control: max-age=" is also specified); the page is not completely loaded when the user navigates away from it or has pending network requests for other reasons (e.g. XMLHttpRequest)); the page has running IndexedDB transactions; the top-level page contains frames (e.g. <iframe> ) that are not cacheable for any of the reasons listed here; the page is in a frame and the user loads a new page within that frame (in this case, when the user navigates away from the page, the content that was last loaded into the frames is what is cached).
想了下如果Firefox可以這樣, Safari和Chrome應該也可以, 於是找到一個非常簡單的方法來解決這個問題, 並且兼容Firefox、Safari、Opera, 只要在頁面中加入下面的代碼:
$(window).unload(function () { });
經過測試, 頁面上一點綁定unload事件, Firefox/Safari/Opera等瀏覽器變會認為該頁面不需要bfcache, 回歸到傳統的Cache模式, 這樣就能解決back時不觸發onload, ready等事件帶來的問題了.
下面有兩個連接, 不過解決問題的思路都差不多.
http://stackoverflow.com/questions/11979156/mobile-safari-back-button
http://stackoverflow.com/questions/24046/the-safari-back-button-problem
