我们知道,浏览器实现了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 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 });
到这里,我们的基本功能实现了:用户进入页面后,第一次点击“回退”并不会离开页面,而是触发弹层,再次点击“回退”离开当前页面。
但是,新的问题出现了。如果页面中有其他hash锚点被点击的时候,页面不会跳转,但会触发popstate
事件,此时浮层便会显示,但此时用户并没有离开页面,并且如果没有在浮层中添加隐藏浮层和重置status变量的逻辑,浮层将一直显示。
于是,我开始寻找如何判断popstate
触发是从初次添加的hash链接跳回页面原始链接的方法。因为,如果不是页面onload
的时候,用脚本pushState添加加了hash的链接,此时页面已经回退跳出了。所以,我开始尝试从popstate
事件的事件对象中寻找链接的变化线路:
但是,很遗憾!我只从对象中发现了进入页面是通过pushState传入的state属性,并没有其他任何特征属性可以帮助到我。而单看这个属性,想要判断页面链接的变化情况,实在是太难了。至少要知道现在是什么,将要变成什么,才能有判断的可能,所以,我还需要找到另一个辅助数据。
我们知道,当页面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/