最近,工作業務有點變化,碰巧遇到一個老生常談的問題——如何去監聽瀏覽器的“後退”事件。其實,情況是這樣的,產品同學希望使用者離開頁面之前,能展現一個漂亮的彈出層給使用者,可以通過這個浮層瞭解使用者離開的原因、或者讓使用者給應用評分等。
我們知道,瀏覽器實現了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
的時候,強制重新整理頁面也有可能導致“後退”路徑異常 - 直接關閉瀏覽器也是沒辦法咯
OK,就這樣了,我們來看一下DEMO演示,進入頁面後就點瀏覽器的“後退”。上面遺留的問題,我暫時還沒有找到處理的方法。革命尚未成功,同志仍須努力~~~