前言
拦截物理返回键的需求其实还是蛮多的,比如,点击返回键关闭弹层,点击返回键避免没有保存就退出,等等。
如何拦截物理返回键这个事,网上有很多搜索结果,但是真正解决问题的文章我硬是没有找到,瞎鸡巴写的倒是很多,所以干脆自行研究了一下,给大家介绍一个方法。
定个需求
现在我的项目中有一个购买商品的弹层,也就是sku,那么,当点击物理回退键时,如果弹层显示着,我就让它隐藏,而且要阻止退回到上一页。
浏览器困境
拦截物理键,原则上是监听popstate事件,然而,现实很残酷,因为popstate事件的意思是路由已经变化完成,也就是说,并没有一种方法真正监听物理键的点击。一旦触发popstate,其实就已经晚了,因为路由已经变化了,你已经无法拦截。
因此,window.addEventListener("popstate", fn)
的回调函数fn,是在浏览器回退之后执行,这个回退是浏览器自己执行的,不受任何控制,无论是加上event.preventDefault()
还是return false
都白扯。这就好比,一个元素已经监听到了click,你无论加什么代码,都不能让浏览器当做没有点击(CSS层面的阻止点击另说)。
怎么办?
只能是给浏览器跪下,先听从它回退,然后再重新push回来。好在,Vue-router给我们提供了push回来的方法。
好了,我们开始。
方法
在需要关闭sku弹层的组件(必须是路由级组件)里加入:
beforeRouteLeave(to, from, next) { // 仅适用于本页面不作为H5着陆页的情况
if (this.skuShow) {
this.$store.commit('SET_SKU_SHOW', false)
next(false)
} else {
next()
}
},
核心代码是next(false)
。根据官方说法:
如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
所以其实也是push回来的道理。
着陆页困境
最后还有一个重要的问题就是,如果你是在项目着陆页(也就是浏览器打开的项目的第一个页)弹了一个sku层,那么这个方案就无效了,道理也很简单,浏览器先回退,有3种可能:
- 可以回退,但退出项目了,退到先前打开的其他网站的页面了,这时候再想push回来也白扯了,因为项目都退了,怎么执行push?
- 无法回退,既然无法回退,那么就不触发beforeRouteLeave钩子,所以也不能执行代码。
- 如果项目是在APP的webview中打开的,一般来讲是不存在第一种情况的,只可能是第二种情况,那么当无法回退的时候,APP会退出webview,也依然不会触发beforeRouteLeave钩子。
所以结论就是:
- web端,想要物理回退键只关闭sku,就要另想办法了,而且几乎没办法,至少我是没办法。
- 如果是在APP的webview中,办法还是有的,就是APP监听物理返回键,监听到之后,调用js环境的window对象上的一个方法,这个方法的作用是读取vuex中的skuShow的值,如果是true,则APP拦截物理键,方法也把skuShow改为false,如果skuShow本身就是false,则正常操作即可。
当然了,首页就出现弹出层这种交互方式,本身也应该避免,因为首页往往是作为导航用的,应该减弱交互。尤其是单纯H5页面,首页一定不要弄弹出层,因为用户随便就退出网站了,最后损失的是你的访问量。