h5全屏弹窗点击后退键时停留当前页
最近本菜鸡在处理h5项目时,小伙伴们已经把组织架构的组件造好了,于是直接就拿着组件开始整活了,一路高歌到测试环节。在测试环节中,由于在首页就用到这个组件,每次打开弹窗后,想关闭时,测试大大们喜欢用虚拟回退键,再加上进企微版应用后,顶栏上自带回退键,用得更是得心应手,屡屡退出应用,抱怨开发垃圾使用不便。由于设计稿UI界面上存在<取消>、<确认>按钮,导致小伙伴先入为主,认为这是一个全屏弹窗组件,但目前又需要全屏页面支持点击虚拟回退键or回退键仅把弹窗关闭而实际路由并不后退。
这不,恰好吹牛的声音略大了些,于是乎就被叫去帮忙解决这个问题。
思考过程
原本是想重新改写成个页面的...但是时间并不充裕,而且取值传参方式也得大改一套,遂弃。
后来想着既然要实现路由操作的话,我模拟加一层路由栈,然后点击后退的时候把加的那一层路由记录给去掉。转头一想,似乎很有操作性的样子,赶紧捋一捋实现一下~~
首先需要模拟添加一层路由,于是联想到浏览器中添加和修改历史记录中条目的俩兄弟,没错就是pushState和replaceState
HTML5 引入了 history.pushState() 和 history.replaceState() 方法,它们分别可以添加和修改历史记录条目。这些方法通常与
window.onpopstate配合使用。使用history.pushState()可以改变referrer,它在用户发送XMLHttpRequest请求时在HTTP头部使用,改变state后创建的XMLHttpRequest对象的referrer都会被改变。因为referrer是标识创建XMLHttpRequest对象时this所代表的window对象中document的URL。
// 新增一条记录
// 与 window.location.href 相比,
// pushState 的优点是任意URL都可以新增一条记录
// 而 window.location.href 对于相同的 URL 则无变化,包括仅变化 hash 部分。
window.history.pushState('状态对象', 'title', 'URL')
// 修改当前的记录项,而不是新增一条记录
window.history.replaceState('状态对象', 'title', 'URL')
- 状态对象:可以是能被序列化的任何东西,但有640k的大小限制。
- title:一个预留参数,可以是存放当前页的标题。
- URL:可以是同源的任意URL
接着需要在点击回退按钮时监听到回退事件,于是联想到onpopstate监听事件
每当活动的历史记录项发生变化时,
popstate事件都会被传递给window对象。如果当前活动的历史记录项是被pushState创建的,或者是由replaceState改变的,那么popstate事件的状态属性state会包含一个当前历史记录状态对象的拷贝。
window.onpopstate = function(event) {
// console.log(event)
// do something...
}
最后如果弹窗没有点击按钮,而想把路由回归更改之前的状态,则需手动处理,将新增路由去除
DOM
window对象通过history对象提供了对浏览器的会话历史的访问(不要与 WebExtensions history搞混了)。它暴露了很多有用的方法和属性,允许你在用户浏览历史中向前和向后跳转,同时——从HTML5开始——提供了对history栈中内容的操作。
// 向前跳转
window.history.forward();
// 向后跳转
window.history.back();
// 跳转到任意一层
// n = 1 时 <==> forward 向前跳转
// n = -1 时 <==> back 向后跳转
window.history.go(n);
解决方案
由于项目是基于Vue框架,所以为了解决所有全屏弹窗的后退问题,就用了mixin
// 用于防止全屏弹窗退出应用
export const popStateWatcher = {
methods: {
// 修改Title
handleEditPageTitle(title) {
window.document.title = title;
},
// 创建监听事件
handleCreatePopWatch() {
window.addEventListener("popstate", this.popStateChange);
},
// 去除监听事件
handleRemovePopWatch() {
window.removeEventListener("popstate", this.popStateChange);
},
// 事件
popStateChange(e) {
const popState = this.handlePopStorage();
this.handleEditPageTitle(popState?.title || "");
this.afterPopChange && this.afterPopChange(e); // 需要单独定义 afterPopChange 处理业务
this.handleRemovePopWatch();
this.handlePopStorage('remove');
// window.history.pushState(popState, popState.title, popState.url);
},
// 按钮调用 -- 浏览器加一层空白路由栈
handleEmptyPopPush(type = "push") {
const popState = {
type,
title: window.document.title,
url: window.location.href,
// url: `${window.location.href.split('#')[0]}#/emptyPage`,
};
this.handlePopStorage('set', popState);
const func = `${type}State`;
window.history[func](popState, popState.title, popState.url);
this.handleCreatePopWatch(); // 点击再添加后退监听
},
// 手动返回上一页
handleGoBackPopURL(order = -1) {
window.history.go(order);
// this.handlePopStorage('remove');
// window.onbeforeunload = function (params) {
// return false;
// };
},
// 判断是不是空栈
handlePopStorage(type = "get", popState = {}, key = "__popState__") {
if (type === "set") return sessionStorage.setItem(key, JSON.stringify(popState));
if (type === "remove") return sessionStorage.removeItem(key);
const JSONP = JSON.parse(sessionStorage.getItem(key));
return JSONP;
},
},
};