【转】浏览器“后退”、“前进”或可以这么去监听

一个老生常谈的问题——如何去监听浏览器的“后退”事件。其实,情况是这样的,产品同学希望用户离开页面之前,能展现一个漂亮的弹出层给用户,可以通过这个浮层了解用户离开的原因、或者让用户给应用评分等。

我们知道,浏览器实现了onbeforeunloadonunload事件,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事件的事件对象中寻找链接的变化线路:

Popstate事件对象

但是,很遗憾!我只从对象中发现了进入页面是通过pushState传入的state属性,并没有其他任何特征属性可以帮助到我。而单看这个属性,想要判断页面链接的变化情况,实在是太难了。至少要知道现在是什么,将要变成什么,才能有判断的可能,所以,我还需要找到另一个辅助数据。

我们知道,当页面hash变化的时候,还会触发hashchange事件。那么,在hashchange的时候,有没有什么可用的数据呢?

于是,我又给页面绑定了hashchange事件,来观察hashchange带来的变化:

1
2
3
window.addEventListener('hashchange', function(ev){
console.log(ev);
});

本来只是想在popstate的基础之上,通过hashchange挖掘到另一个可用的数据,却没想到有了意外的发现:

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演示进入页面后就点浏览器的“后退”。上面遗留的问题,我暂时还没有找到处理的方法。革命尚未成功,同志仍须努力~~~

posted @ 2017-11-30 10:12  搬砖的夕夕  阅读(1007)  评论(0编辑  收藏  举报