luokuning / blogs

翻译,随笔,以及懒得整理……
81 stars 2 forks source link

如何监听用户点击浏览器后退按钮 #3

Open luokuning opened 8 years ago

luokuning commented 8 years ago

突发奇想,能不能监听点击浏览器的后退按钮事件。

  1. 很明显,如果后退是跳转到另一个全新的页面,监听 window.onbeforeunload 即可

    实际上有很多事件都会触发 onbeforeunload,比如刷新、点击链接前往新的页面等。这里应该还是在页面加载完成比如 window.onload 事件里使用 history.pushState 推一条记录进栈,同时监听 window.onpopstate 事件,在这个事件监听器里处理回退。其实也就变得跟下面的两个方案差不多了。

  2. 如果整个页面是一个SPA,而且使用的是 pushState/replaceState 来实现的导航,那么当浏览器回退的时候会触发 window 上的 popstate 事件。这时需要监听 window.onpopstate 事件;
  3. 如果整个页面是一个SPA,使用的是基于 hashChange 实现的导航,那么这个时候就比较麻烦。因为页面根据 hash 正常变化跳转(导航)的时候也会触发 popstate 事件,所以不能简单的监听 popstate 事件来做出判断。开始的时候对于这种情况我也没有解决思路,于是带上梯子墙内外找了一通。

果不其然,stackoverflow 上一个问题里的一个答案 (点来点去不小心把链接丢了:cry:) 给了一个思路:因为后退按钮属于浏览器的UI,并不属于任何一个页面,所以如果当鼠标在当前页面外时,监听的 popstate 事件被触发,那么认为用户点击了浏览器的后退按钮 (这里的判断鼠标是否在当前页面里是通过监听 document.onmouseleavedocument.onmouseenter事件);另外答案里还绑定了对 Backspace 键的监听,因为某些浏览器默认当页面焦点不属于输入域时触发的是后退操作。这个方法基本可以满足大多数情况,但是如果用户是通过鼠标侧键 (常见于游戏鼠标) 来实现的后退操作,那么可能就无法监听了。

我一直相信黄天不负有心人这句话在大多数情况下都是可以被验证的,而且也相信自己的运气不会太差,因为我爱笑。好了回到正题,说实话对于怎么解决这个问题我想了整整一下午,最后不知不觉敲下了这么一段代码:

    var detectBack = {

        initialize: function() {
            //监听 hashchange 事件
            window.addEventListener('hashchange', function() {

                //为当前导航页附加一个 tag
                this.history.replaceState('hasHash', '', '');

            }, false);

            window.addEventListener('popstate', function(e) {

                if (e.state) {
                    //侦测是用户触发的后退操作, dosomething
                    //这里刷新当前 url
                    this.location.reload();
                }
            }, false);
        }
    }

    detectBack.initialize();

这样就不用监听鼠标或键盘事件了:grin:。代码很简单,也有详细的注释,这里就不在花篇幅解释了,有疑问的话就请留言吧。

更新 (2016/12/22):

其实前进后退按钮都会触发 popstate 事件,MDN 里解释说每当激活的历史记录发生变化时,都会触发 popstate 事件。

更新 (2019/12/19)

在新版的 Chrome 中 (我测试的版本是 79,具体从哪一版本开始的变更暂不清楚),如果你的页面不是单页应用,而且采用的是在 initialize 方法中手动执行 history.pushState(1, '', '') 推入一条历史记录从而监听历史栈的变动,那么如果用户没在目标页面做任何操作(点击、输入等等),直接点击回退按钮的话,是无法触发 popstate 事件的,也就是说这个方法会失效。

Superb-x commented 8 years ago

赞一个,学到了

songdengwei commented 7 years ago

感谢你的答案非常好,但是我还想区分点击是后退还是前进,这个怎么去判断呢?

luokuning commented 7 years ago

@songdengwei 之前没有考虑到前进按钮,其实前进按钮也会触发 popstate 事件。如果是使用了 HTML5 的 history api 的单页应用,可以在 pushState 的时候给要入栈的状态对象加上一个字段来区分历史栈中每个状态的先后顺序,比如 pushState({s: 'a.html', index: 0}, '', 'a.html')pushState({s: 'b.html', index: 1}, '', 'b.html'),这样当点击浏览器前进后退按钮的时候我们可以通过比较 e.state.index 和当前 history.state.index 的值来判断具体是入栈还是出栈,不过这只是一个初始的想法,并没有实践过。 如果是多页面应用的话还没有想到什么方法来区分前进后退按钮。

TheSaltwaterRoom commented 7 years ago

浏览器的回退按钮可以用popstate事件监听,为什么我点击浏览器的后退按钮监听不到呢?

luokuning commented 7 years ago

@TheSaltwaterRoom 请问你是直接在控制台输入的类似 window.onpopstate 这样的监听代码吗?popstate 事件被触发是在激活状态的历史条目发生改变的时候,也就是说在监听之前应该先用 history.pushState 方法往历史栈中推一条数据,这个时候点击回退按钮才会触发 popstate 事件。

TheSaltwaterRoom commented 7 years ago

我现在在代码里直接写的是if (history.pushState) {} 这样可以监听到,大神可以加一下QQ好友吗?

luokuning commented 7 years ago

@TheSaltwaterRoom 不怎么用 qq... 这里有代码高亮很方便,你可以直接在这里贴出比较完整一点的代码哦

TheSaltwaterRoom commented 7 years ago

我用这种方法监听浏览器后退按钮就监测不到,然后只能用 if(history.pushState),才可以.一开始我用H5的localStorage来缓存我需要的数据,然后在用户点击手机上的返回键的时候,再把缓存的信息读取出来 window.addEventListener('popstate', function(e) { }, false);

iamstd commented 7 years ago

在window.addEventListner前面调用一下history.pushState就可刷新。 var detectBack = {

    initialize: function() {
       console.log('stat2e', window.addEventListener)
        //监听hashchange事件
        window.addEventListener('hashchange', function() {
          console.log(111)
            //为当前导航页附加一个tag
            this.history.replaceState('hasHash', '', '');

        }, false);

        history.pushState(1, '', '')
        window.onpopstate = function(e) {
          console.log(e, e.state)
            if (!e.state) {
              //侦测是用户触发的后退操作, dosomething
              //这里刷新当前url
              window.location.reload();
            }
        };
    }
}
Porisika commented 6 years ago

太感谢了,帮我解决了这个棘手的问题!

lockzerlw commented 6 years ago

赞!

langerTian commented 6 years ago

感谢楼主,但是有个疑问,这个监听用户后退事件是不是只能监听地址发生改变,但是页面不刷新的情况?

xiaosatufu commented 6 years ago

果然还是要上google才容易找得到答案。

ntnyq commented 6 years ago

学习了。 👍

qiuziz commented 5 years ago

有个场景是 支付时打开第三方支付页面,支付成功返回自己的页面,根据返回的状态判断replaceState跳转支付成功页,这时在成功页点击返回会跳到第三方,然后第三方根据订单号判断已经支付又重定向到成功页,造成循环。目前在成功页监听返回已经ok了,在成功页点击返回 监听到了就go(-3),跳转到商品详情页,但是这时候还是可以点击前进的,安卓上不好处理

qiuziz commented 5 years ago

不好意思 已经解决了 在返回到商品详情页时,再次pushState一个#就把之前的清空了 thx

zhaomenglu commented 4 years ago

我是在本地建了两个html页面,从页面A跳到页面B,然后在B页面的时候点击浏览器的回退,还是跳到了A页面,感觉就是没生效。。我就直接把这个代码复制在页面B上了

luokuning commented 4 years ago

@zhaomenglu 如果是在你这种情况下测试的话,上面的代码需要在 initialize 方法里加一条 history.pushState(1, '', ''),主动推一条记录到历史栈中。不过有一点需要注意,对于新版的 Chrome 浏览器,跳转到 B 页面之后,用户需要在 B 页面里做一些操作(比如点击元素、输入等等),才能在点击回退按钮的时候触发 popstate 事件。

xiaoxiao78 commented 4 years ago

google 版本85 也需要进行一些操作才可以出发

CNWebArmy commented 3 years ago

在 vue 中,页面挂载的时候添加监听,还是监听不到回退,求解