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