瀏覽器「後退」、「前進」或能夠這麼去監聽

咱們知道,瀏覽器實現了onbeforeunloadonunload事件,onbeforeonload事件是在瀏覽器即將請求下一個頁面(請求還未發出)的時候觸發,它能夠實現阻止onunload的觸發。onunload事件則是瀏覽器已經將下一個頁面請求回來,頁面即將跳轉的時候觸發,該事件沒法中斷。看起來onbeforeunload事件彷佛能知足咱們的需求,可是,這只是一個假象。html

onbeforeunload事件雖然能阻止onunload事件的觸發,可是因爲它是瀏覽器內置的事件,其出現的交互方式和UI界面,均由瀏覽器廠商控制,並未提供給開發者定義浮層內部內容更多交互的接口,甚至文本性質的提示內容也沒法設置樣式。因此,想要經過onbeforeunload事件提供的浮層實現收集用戶離開的緣由或讓用戶給應用打分的功能並不現實。web

下面我就詳細描述下我作的思路,不過我要先聲明如下幾點:瀏覽器

  • 該方案只能部分解決需求,並不能完美解決問題
  • 這只是一種嘗試,並未正式應用於業務
  • 該方案涉及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時出現彈層便可: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>

 轉載至原文:http://web.jobbole.com/89526/

相關文章
相關標籤/搜索