咱們知道,瀏覽器實現了onbeforeunload
和onunload
事件,onbeforeonload
事件是在瀏覽器即將請求下一個頁面(請求還未發出)的時候觸發,它能夠實現阻止onunload
的觸發。onunload
事件則是瀏覽器已經將下一個頁面請求回來,頁面即將跳轉的時候觸發,該事件沒法中斷。看起來onbeforeunload
事件彷佛能知足咱們的需求,可是,這只是一個假象。html
onbeforeunload
事件雖然能阻止onunload
事件的觸發,可是因爲它是瀏覽器內置的事件,其出現的交互方式和UI界面,均由瀏覽器廠商控制,並未提供給開發者定義浮層內部內容更多交互的接口,甚至文本性質的提示內容也沒法設置樣式。因此,想要經過onbeforeunload
事件提供的浮層實現收集用戶離開的緣由或讓用戶給應用打分的功能並不現實。web
下面我就詳細描述下我作的思路,不過我要先聲明如下幾點:瀏覽器
history.pushState
方法、popstate
事件以及功臣hashchange
事件在進入主題以前,咱們先來羅列幾個小知識點:函數
history.pushState
能夠改變地址欄連接地址,但不觸發頁面刷新(不離開)popstate
事件和hashchange
事件popstate
事件對象能夠得到pushState傳遞進去的state屬性,從而獲得變化後的連接地址等hashchange
事件對象中包含變化先後的連接地址(oldURL和newURL)hashchange
事件 我首先想到的是,當頁面加載完成時,經過status變量標記頁面狀態爲0。利用代碼push一個連接到history中,status狀態改成1,標記此時連接變化了,但頁面並未刷新。當用戶點擊瀏覽器「後退」按鍵的時候,瀏覽器地址首先返回頁面的原始連接地址,頁面並不會刷新,此時觸發popstate
事件,只需在事件函數中判斷status === 1
時出現彈層便可:spa
1 var status = 0, 2 // 存儲浮層節點 3 pop = document.getElementById('J_PageWrap'); 4 window.addEventListener('load', function() { 5 var tit = document.title, 6 path = location.href.replace(/#.*$/, '') + '#!hash'; 7 // 將追加了hash的連接推入history中 8 history.pushState({title: tit, path: path}, tit, path); 9 status = 1; 10 }); 11 window.addEventListener('popstate', function(ev){ 12 if (status == 1) { 13 status = 0; 14 pop.className += ' show'; // show爲顯示浮層樣式 15 } 16 });
到這裏,咱們的基本功能實現了:用戶進入頁面後,第一次點擊「回退」並不會離開頁面,而是觸發彈層,再次點擊「回退」離開當前頁面。code
可是,新的問題出現了。若是頁面中有其餘hash錨點被點擊的時候,頁面不會跳轉,但會觸發popstate
事件,此時浮層便會顯示,但此時用戶並無離開頁面,而且若是沒有在浮層中添加隱藏浮層和重置status變量的邏輯,浮層將一直顯示。htm
因而,我開始尋找如何判斷popstate
觸發是從初次添加的hash連接跳回頁面原始連接的方法。由於,若是不是頁面onload
的時候,用腳本pushState添加加了hash的連接,此時頁面已經回退跳出了。因此,我開始嘗試從popstate
事件的事件對象中尋找連接的變化線路:對象
可是,很遺憾!我只從對象中發現了進入頁面是經過pushState傳入的state屬性,並無其餘任何特徵屬性能夠幫助到我。而單看這個屬性,想要判斷頁面連接的變化狀況,實在是太難了。至少要知道如今是什麼,將要變成什麼,纔能有判斷的可能,因此,我還須要找到另外一個輔助數據。blog
咱們知道,當頁面hash變化的時候,還會觸發hashchange
事件。那麼,在hashchange
的時候,有沒有什麼可用的數據呢?接口
因而,我又給頁面綁定了hashchange
事件,來觀察hashchange
帶來的變化:
window.addEventListener('hashchange', function(ev){ console.log(ev); });
原本只是想在popstate
的基礎之上,經過hashchange
挖掘到另外一個可用的數據,卻沒想到有了意外的發現:
hashchange
的時間對象中,居然內置了變化前(oldURL)後(newURL)的兩個連接地址。這樣一來,popstate
的那段邏輯,在這裏彷佛就沒那麼必要了。因而,我將代碼改形成了這樣:
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
的時候,強制刷新頁面也有可能致使「後退」路徑異常示例DEMO:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width,initial-scale=1"> 6 <title>HASHCHANGE</title> 7 <style> 8 html, body { 9 margin: 0; 10 padding: 0; 11 height: 100%; 12 overflow: hidden; 13 } 14 .page-wrap { 15 position: absolute; 16 top: 100%; 17 height: 30%; 18 width: 100%; 19 font-size: 250px; 20 line-height: 30vh; 21 text-align: center; 22 background-color: #f00; 23 color: #fff; 24 -webkit-transition: top .3s ease-out; 25 -o-transition: top .3s ease-out; 26 transition: top .3s ease-out; 27 } 28 .page-wrap.show { 29 top: 70%; 30 } 31 .page-main { 32 text-align: center; 33 line-height: 100vh; 34 font-size: 10vw; 35 font-weight: 600; 36 } 37 </style> 38 </head> 39 <body> 40 <div class="page-main"> 41 <a href="#changeHash" target="_self">SecondPage</a> 42 </div> 43 <div id="J_PageWrap" class="page-wrap">0</div> 44 <script> 45 (function(){ 46 var status = 0, 47 pop = document.getElementById('J_PageWrap'); 48 window.addEventListener('load', function() { 49 var tit = document.title, 50 path = location.href.replace(/#.*$/, '') + '#!hash'; 51 history.pushState({title: tit, path: path}, tit, path); 52 status = 1; 53 }); 54 // window.addEventListener('popstate', function(ev){ 55 // console.log(ev); 56 // if (status == 1) { 57 // status = 0; 58 // pop.className = 'page-wrap show'; 59 // } 60 // }); 61 window.addEventListener('hashchange', function(ev){ 62 // console.log(ev); 63 var oAddr = ev.oldURL.replace(/^.+(?=\/\/)/, ''), 64 nAddr = ev.newURL.replace(/^.+(?=\/\/)/, ''); 65 if (oAddr === '//seejs.com/demos/examples/goback/index.html#!hash' 66 && nAddr === '//seejs.com/demos/examples/goback/index.html') { 67 pop.className += ' show'; 68 } else { 69 pop.className = 'page-wrap'; 70 } 71 }); 72 })(); 73 </script> 74 </body> 75 </html>