摘要:對于那些老網站或者老項目來說全盤改造成并不現實,于是就有了局部頁面刷新這個解決方案。如果不知道局部頁面刷新是何物請看這里,這里和這里。但實際上,第一次后退無法還原的內容陷阱,第二次后退頁面刷新了一切恢復最初的樣子。
ajax在現代網站已經得到非常普遍地應用,主要的好處大家都知道(異步加載數據,不用刷新整個瀏覽器,更小的數據傳輸尺寸)。對于那些老網站或者老項目來說全盤改造成ajax并不現實,于是就有了“局部頁面刷新”這個解決方案。如果不知道“局部頁面刷新”是何物請看這里,這里和這里。
在我們的項目里,將原來的iframe或者frame統統替換成了時髦的div,然后修改了頁面上所有發起請求的地方,把響應內容jQuery.load到div里。
于是乎原來老舊的網站變成了一個時髦的基于ajax的網站,每個頁面傳輸的數據量變小了,再也不用解決令人頭疼的:
為了消除滾動條讓iframe自適應大小
如何訪問parent window變量的問題(還有如何訪問parent的parent的parent... window變量的問題)
如何訪問child iframe里的變量[]的問題了(還有如何訪問child的child的child... iframe里的變量的問題)
因為大家永遠都在同一個window里,而且div本身就會根據內容自動撐大。但是等等!瀏覽器怎么不能后退了?
我們的那個項目是一個滿大街可見的XX管理信息系統,這種系統最常見的布局就是左側一個樹形菜單區域,右側是一個功能區域,功能區域里有一個查詢條件區域(里面有個查詢按鈕),還有一個空白的區域用來顯示查詢結果,同時是用戶操作數據的地方(比如form表單)。
在iframe時代,上面講到的4個區域都是一個iframe,這也就意味著我們可以有很{{BANNED}}的后退能力。
當然了一般來說用戶最常用的就是對操作區域做后退動作,比如查詢一下,選擇一條記錄點擊修改,看到form表單,修改一下,在點擊保存前后悔了,點擊瀏覽器的后退,回到查詢結果頁面。
但是在引入了ajax后無法后退了,因為ajax請求不會記錄到瀏覽器歷史里,歷史都沒有了自然就無法后退了。
好在Html5的History API能夠幫助我們解決問題。我們可以人為的使用history.pushState來人造歷史信息,并且通過監聽popstate事件來知道用戶點擊了瀏覽器后退或前進按鈕,然后將頁面元素還原到歷史上的某個狀態。關于Html5 History API的相關信息可以看這里。
但是事情遠不止這么簡單,下面是我們遇到的一些坑:
陷阱1:重復執行js腳本// 點擊查詢按鈕的時候人為構造一個瀏覽器歷史 $("#some-button").click(function() { $(targetSelector).load(url); history.pushState({ container : targetSelector, content : $(targetSelector).html() }, null, url); }); // 當瀏覽器后退后者前進的時候,我們把當時的結果重新加載到container里來 window.addEventListener("popstate", function() { var state = history.state $(state.container).html(state.content); })
一切看上去都OK,直到...我們發現局部頁面刷新所獲得的結果里包含了操作dom元素的js。
當遇到這種情況時會發生很奇妙的現象,history state.content是已經加載完畢+js執行后的結果,當我們重新還原的時候,我們會把這個結果加載出來,并且又會執行一遍js。如果這個js是一個添加dom的動作那么在后退的時候你會看到這個重復的dom元素。
我們想過跟蹤哪些dom元素是被js修改過的來避免這個問題,但是...這是不現實的。
陷阱2:無法還原到最初狀態前面的方案因為load的內容里可能有js腳本所以有嚴重缺陷,于是我們換了個思路,history里保存responseText,而不是已經load好后的東西。
// 點擊查詢按鈕的時候人為構造一個瀏覽器歷史 $("#some-button").click(function() { $(targetSelector).load(url, function(responseText) { history.pushState({ container : targetSelector, content : responseText }, null, url); }); }); // popstate事件的處理方式一樣
但是仍然遇到了這么一個問題,如果container(刷新目標區域,某個div)原來是有內容的,而這個內容不是通過ajax局部頁面刷新而來,而是用戶一進入這個頁面就已經有的,比如使用服務器端的模板引擎生成的頁面,那么在它加載完html片段后就無法回退了。因為它的內容一開始就不在history里(事實上瀏覽器自己產生的history是沒有state的),這樣就形成了退無可退的局面。
如果你想,我們只要保存這個container原來的內容不就行了,當后退的時候我們直接恢復它原來的內容,但是請看陷阱1
不過當發生退無可退的情況時,我們認為已經退回到了第一次進入頁面的狀態,這個時候我們刷新整個頁面就行了。
陷阱3:多個并列的container陷阱2的解決方案實際上是基于container之間是屬于嵌套關系或者就一個container的情況的。如果是這種情況就不行了:
有A和B兩個container,點擊某個按鈕刷新了A的內容(產生歷史),然后在點擊某個按鈕刷新的B的按鈕(產生歷史),按照用戶的預想情況,第一次后退還原B原來的內容,第二次后退還原A原來的內容。但實際上,第一次后退無法還原B的內容(陷阱2),第二次后退頁面刷新了(一切恢復最初的樣子)。
如果B是嵌套在A里的就無所謂了,第一次后退的時候獲得的是A的state,根據A的state還原A的內容的時候順便把B也還原了,第二次后退頁面刷新,把A也還原了。
而且根據陷阱1所講,我們也不能在history里存儲A或者B里原來的內容。
解決辦法:對于這種操作就不要記錄歷史了。
陷阱4:看到過時頁面我們在History state里存的是當時load時的responseText,當我們后退的時候看到的是過時的頁面,比如我們原先查詢結果里看到有A記錄,然后我們跳轉到其他頁面里,然后再后退到查詢結果頁面看到A記錄還在,但是這個A記錄很可能只是一個幽靈,在數據庫里早就已經不存在了。如果我們這個時候再對A記錄操作就有出現錯誤。
解決辦法是我們在history state里保存url已經相關的參數,當popstate的時候重新發起請求就行了,這樣一來的話也減少了history存儲state所需要的空間。
// 這里只給get請求的例子,post的原理也差不多 $("#some-button").click(function() { $(targetSelector).load(url, function(responseText) { history.pushState({ container : targetSelector, url : url }, null, url); }); }); window.addEventListener("popstate", function() { var state = history.state; $(state.container).load(state.url); });陷阱5:redirect
即使我們在history state保存了url你就以為沒事了?too simple, too naive!如果我們對這個url發起的請求被服務器redirect到另一個url,那么在history state里保存這個url就不對了。
如果我們這個url是用來刪除某條記錄的,服務器收到請求在數據庫里刪除了這條記錄,然后redirect到了首頁url,那么這個時候你在history里應該存那個url呢?顯然是首頁的url,因為如果你存了刪除url,那么在后退的時候,我們會重新發起這個url,想想這多嚇人。
解決辦法其實不太簡單,因為ajax是否被redirect你是不知道的,用jQuery封裝的jqXHR對象也沒法知道這個。
也許鏈WHATWG的XmlHttpRequest.responseURL可以救你,但是瀏覽器兼容性不好。
我的做法在服務器sendRedirect之前在requestUrl的queryString里添加一個flag,用一個專門的servlet filter判斷過來的請求是否有這個flag,如果有那么就將本次請求的url(也就是redirect到的url)放到response的一個特定的header里。然后就可以用jqXHR.getResponseHeader("some-header")來獲得這個url,把這個url放到history state里。
陷阱6:無法精確還原dom對象的狀態不論是保存responseText還是保存url請求參數,都無法在瀏覽器后退的時候精確還原dom對象的狀態,比如我在IE6里有個這樣的特性,你在某個頁面勾選了某個checkbox,然后跳轉到一個新的頁面然后再后退,那個checkbox還是處于勾選狀態,這個在利用ajax局部頁面刷新里是完全做不到的,想到用戶和我說以前后退的時候那個勾還在現在勾沒有了,不解決這個BUG就不驗收的事情時才想到iframe的好啊。
所以如果要精確還原dom對象的狀態,得在history.pushState的時候自行把相關信息保存下來,在popstate的時候用到這些信息并還原dom。
事實上即使用了iframe也并不是所有的瀏覽器會還原dom對象狀態,看這篇文章。
總結不要輕易從iframe切換到ajax局部頁面刷新
要自己控制那些ajax局部頁面刷新紀錄歷史,哪些不記錄,有些時候可能還需要replaceState,不要想當然的把所有請求都記錄歷史
把代碼改造成ajax局部頁面刷新只是第一步,還需要對整個網站、應用的UI做規劃和設計,關于這個問題不存在通用的解決方案
參考資料MANIPULATING HISTORY FOR FUN & PROFIT
Session history and navigation
Manipulating the browser history
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80052.html
摘要:而唯一不引發刷新的參數并不會發送到服務器,因此服務器無法獲得狀態。目前建議設置為空字符串。此外請注意,及本身調用時是不觸發事件的。我認為,按照漸進增強的思路,這樣就是最好的了,也就是只使用較少的代碼優化高級瀏覽器的使用體驗。 HTML5 history API有什么用呢? 從Ajax翻頁的問題說起 請想象你正在看一個視頻下面的評論,在翻到十幾頁的時候,你發現一個寫得稍長,但非常有趣的評...
摘要:初步理解如果最近打電話給武漢的小伙伴,他說信號不好,那么相信我,他肯定不是真的信號不好,也不是不想和你說話,而是他可能在冰箱里。。。 初步理解 如果最近打電話給武漢的小伙伴,他說信號不好,那么相信我,他肯定不是真的信號不好,也不是不想和你說話,而是他可能在冰箱里。。。武漢的天氣從來都是喜怒無常的,是吧,屌絲氣十足,今年也是絲毫看不出有任何逆襲的跡象和可能性,當然咱也沒必要去操那個心;好...
摘要:單頁面應用的出現依然存在著爭議性,我們該如何看待他的兩面性呢接下來小生給大家總結一下他的優缺點。單頁面應用的優勢無刷新體驗沒有了令人詬病的頁面頻繁刷新,同時節約瀏覽器資源,路由響應比較及時,提升了用戶的體驗。 前端猿一天不學習就沒飯吃了,后端猿三天不學習仍舊有白米飯擺于桌前。IT行業的快速發展一直在推動著前端技術棧在不斷地更新換代,前端的發展成了互聯網時代的一個縮影。而單頁面應用的發展...
閱讀 878·2021-10-13 09:39
閱讀 3531·2021-09-26 10:16
閱讀 2861·2019-08-30 15:54
閱讀 1037·2019-08-30 14:22
閱讀 2886·2019-08-29 15:39
閱讀 3253·2019-08-27 10:52
閱讀 809·2019-08-26 13:59
閱讀 1703·2019-08-26 12:20