最近,工作業務有點變化,碰巧遇到一個老生常談的問題——如何去監聽瀏覽器的“後退”事件。其實,情況是這樣的,產品同學希望用戶離開頁面之前,能展現一個漂亮的彈出層給用戶,可以通過這個浮層瞭解用戶離開的原因、或者讓用戶給應用評分等。
我們知道,瀏覽器實現了onbeforeunload
和onunload
事件,onbeforeonload
事件是在瀏覽器即將請求下一個頁面(請求還未發出)的時候觸發,它可以實現阻止onunload
的觸發。onunload
事件則是瀏覽器已經將下一個頁面請求回來,頁面即將跳轉的時候觸發,該事件無法中斷。看起來onbeforeunload
事件似乎能滿足我們的需求,但是,這只是一個假象。
onbeforeunload
事件雖然能阻止onunload
事件的觸發,但是由於它是瀏覽器內置的事件,其出現的交互方式和UI界面,均由瀏覽器廠商控制,並未提供給開發者定義浮層內部內容更多交互的接口,甚至文本性質的提示內容也無法設置樣式。所以,想要通過onbeforeunload
事件提供的浮層實現收集用戶離開的原因或讓用戶給應用打分的功能並不現實。
那麼,我們該怎麼辦呢?
思來想去,頭屑掉了一地,終於好像有了那麼一丟丟靈感。下面我就詳細描述下我做的思路,不過我要先聲明以下幾點:
- 該方案只能部分解決需求,並不能完美解決問題
- 這只是一種嘗試,並未正式應用於業務
- 該方案涉及
history.pushState
方法、popstate
事件以及功臣hashchange
事件
在進入主題之前,我們先來羅列幾個小知識點:
- 瀏覽器離開一個頁面,意味着鏈接地址(不含hashchange、pushState方式)發生變化
history.pushState
可以改變地址欄鏈接地址,但不觸發頁面刷新(不離開)- hash變化會觸發
popstate
事件和hashchange
事件 popstate
事件對象可以獲得pushState傳遞進去的state屬性,從而得到變化後的鏈接地址等hashchange
事件對象中包含變化前後的鏈接地址(oldURL和newURL)- 瀏覽器的“前進”、“後退”可以觸發
hashchange
事件
好了,進入正題。我首先想到的是,當頁面加載完成時,通過status變量標記頁面狀態爲0。利用代碼push一個鏈接到history中,status狀態改爲1,標記此時鏈接變化了,但頁面並未刷新。當用戶點擊瀏覽器“後退”按鍵的時候,瀏覽器地址首先返回頁面的原始鏈接地址,頁面並不會刷新,此時觸發popstate
事件,只需在事件函數中判斷status === 1
時出現彈層即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var status = 0, // 存儲浮層節點 pop = document.getElementById('J_PageWrap'); window.addEventListener('load', function() { var tit = document.title, path = location.href.replace(/#.*$/, '') + '#!hash'; // 將追加了hash的鏈接推入history中 history.pushState({title: tit, path: path}, tit, path); status = 1; }); window.addEventListener('popstate', function(ev){ if (status == 1) { status = 0; pop.className += ' show'; // show爲顯示浮層樣式 } }); |
到這裏,我們的基本功能實現了:用戶進入頁面後,第一次點擊“回退”並不會離開頁面,而是觸發彈層,再次點擊“回退”離開當前頁面。
但是,新的問題出現了。如果頁面中有其他hash錨點被點擊的時候,頁面不會跳轉,但會觸發popstate
事件,此時浮層便會顯示,但此時用戶並沒有離開頁面,並且如果沒有在浮層中添加隱藏浮層和重置status變量的邏輯,浮層將一直顯示。
於是,我開始尋找如何判斷popstate
觸發是從初次添加的hash鏈接跳回頁面原始鏈接的方法。因爲,如果不是頁面onload
的時候,用腳本pushState添加加了hash的鏈接,此時頁面已經回退跳出了。所以,我開始嘗試從popstate
事件的事件對象中尋找鏈接的變化線路:
但是,很遺憾!我只從對象中發現了進入頁面是通過pushState傳入的state屬性,並沒有其他任何特徵屬性可以幫助到我。而單看這個屬性,想要判斷頁面鏈接的變化情況,實在是太難了。至少要知道現在是什麼,將要變成什麼,纔能有判斷的可能,所以,我還需要找到另一個輔助數據。
我們知道,當頁面hash變化的時候,還會觸發hashchange
事件。那麼,在hashchange
的時候,有沒有什麼可用的數據呢?
於是,我又給頁面綁定了hashchange
事件,來觀察hashchange
帶來的變化:
1 2 3 | window.addEventListener('hashchange', function(ev){ console.log(ev); }); |
本來只是想在popstate
的基礎之上,通過hashchange
挖掘到另一個可用的數據,卻沒想到有了意外的發現:
hashchange
的時間對象中,竟然內置了變化前(oldURL)後(newURL)的兩個鏈接地址。這樣一來,popstate
的那段邏輯,在這裏似乎就沒那麼必要了。於是,我將代碼改造成了這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var pop = document.getElementById('J_PageWrap'); window.addEventListener('load', function() { var tit = document.title, path = location.href.replace(/#.*$/, '') + '#!hash'; history.pushState({title: tit, path: path}, tit, path); }); window.addEventListener('hashchange', function(ev){ var oAddr = ev.oldURL.replace(/^.+(?=\/\/)/, ''), // 爲避免http(s)的影響,去除協議進行判斷 nAddr = ev.newURL.replace(/^.+(?=\/\/)/, ''); if (oAddr === '//10.14.132.43:808/tests/hash/index.html#!hash' && nAddr === '//10.14.132.43:808/tests/hash/index.html') { pop.className += ' show'; } else { pop.className = 'page-wrap'; } }); |
當且僅當鏈接從帶有#!hash
返回頁面原始鏈接的時候,設置浮層顯示,否則浮層隱藏,這樣就有比前面popstate
的實現又進了一步。
至此,我們不僅保證了頁面的正常操作,也實現了當用戶點擊瀏覽器“後退”按鈕至即將離開頁面的時候出現浮層,收集信息的需求。但是,還有很多問題仍然存在:
- 如果用戶進入過其他頁面,再返回當前頁面點擊“前進”按鈕的時候,並不能觸發浮層
- 在帶有
#!hash
的時候,強制刷新頁面也有可能導致“後退”路徑異常 - 直接關閉瀏覽器也是沒辦法咯
原文地址:http://web.jobbole.com/89526/