赞
踩
H5开发返回上一页是一个很辣手的问题。开发中,我们一般用history.go(-1)返回上一页,原生返回按钮的动作也是history.go(-1)。由于业务需求,某些页面返回需要go(-2)或go(-3)、go(-n),有些页面返回时还有加一个弹窗,让用户再次确认是否离开,这些都有监听物理机的返回事件。开发时,思维打结了,没想到“监听物理机的返回事件”这关键句子,踩了挺多坑的,本文就是记录一下小编的踩坑过程。
前几天就遇到了一个需求,离开正在编辑的页面时,弹窗提示用户,尚未保存编辑信息,是否确定离开。确定则离开,取消则留下继续编辑。当时觉得很简单,调用window.onbeforeunload便可,很快实现了这个需求。
- // beforeunload实现,优点:简单,缺点:弹窗不能自定义,而且兼容性不好
- window.addEventListener('beforeunload', (event) => {
- // Cancel the event as stated by the standard.
- event.preventDefault();
- // Chrome requires returnValue to be set.
- event.returnValue = '';
- });
-
beforeunload的缺点太多,局限性太大,个人觉得不是一个好的方案,于是有了下面的踩坑。
由于所有的返回上一页都是调用history.go(-1)。于是去百度go(-1)的监听事件popstate,然后在监听回调里让自定义弹窗展示。思路是没错,可弹窗没弹出来。于是在进入页面前调localStorage加了个缓存,在监听的回调里把缓存清除,测试回调是否执行。是的,回调没有执行,心里一千个草泥马在奔腾,一度怀疑这api是假的。
于是把MDN文档的例子复制过来,原来是思维打结了。
- window.addEventListener('popstate', (event) => {
- console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
- });
- history.pushState({page: 1}, "title 1", "?page=1");
- history.pushState({page: 2}, "title 2", "?page=2");
- history.replaceState({page: 3}, "title 3", "?page=3");
- history.back(); // Logs "location: http://example.com/example.html?page=1, state: {"page":1}"
- history.back(); // Logs "location: http://example.com/example.html, state: null
- history.go(2); // Logs "location: http://example.com/example.html?page=3, state: {"page":3}
-
由于小编开发是多页应用,go(-1)就直接返回上一页了,没触发监听。要触发监听,进入页面后必须使用了history.pushState()(该api可以改变浏览器历史记录,可以改变URL还不会触发页面重新请求。)添加一条自定义历史记录。这时再调go(-1),浏览器检测不到url变化,就不会重载页面,这时监听才能触发。
-
- function doCcallback() {
- // 自定义弹窗
- var r = confirm("是否退出");
- if (r == true) {
- history.go(-1);
- } else {
- addState();
- }
- }
-
- // 监听浏览器返回
- window.addEventListener("popstate", function (e) {
- if (history.state === null || history.state._is !== "first") {
- doCcallback();
- }
- });
-
- // 使用异步,目的是等待浏览器历史记录发生变化后在添加自定义记录,这样自定义记录会准确添加到栈顶
- setTimeout(function () {
- if (history.state === null || history.state._is !== "first") {
- addState();
- }
- }, 300);
-
- function addState() {
- // 添加浏览器历史记录,url就使用当前的页面的url
- history.pushState({ _is: "first" }, "", location.href);
- }
-

查了一下history.pushState的兼容性,兼容性也还可以。有时候就这么神奇,突然脑袋又多了另一个想法,也可以添加历史记录不触发浏览器请求。
- function doCcallback() {
- // 自定义弹窗
- var r = confirm("是否退出");
- if (r == true) {
- history.go(-1);
- } else {
- addState();
- }
- }
-
- // 监听路由hash值是否改变
- window.addEventListener("hashchange", function (e) {
- // 判断存在hash值才进入
- if ("#goback" !== location.hash) {
- doCcallback();
- }
- });
-
- // 使用异步,目的是等待浏览器历史记录发生变化后在添加自定义记录,这样自定义记录会准确添加到栈顶
- setTimeout(function () {
- if ("#goback" !== location.hash) {
- addState();
- }
- }, 300);
-
- function addState() {
- // 添加浏览器历史记录
- location.hash = "goback";
- }
-

该方法优点:兼容性好,缺点:在SPA应用中可能会冲突。有时候就这么神奇,突然脑袋又多了另一个想法。记得vue-router使用了两种路由实现方式,供用户选择。
- function MonitorBack(config) {
- if (Object.prototype.toString.call(config) !== "[object Object]") {
- config = {};
- }
-
- if (Object.getPrototypeOf(this).constructor !== MonitorBack) {
- return new monitorBack();
- }
-
- var mode = config.mode || "hash"; // 模式
- var self = this;
- var history = window.history;
- var location = window.location;
- var methods = {
- // 存储事件回调
- goback: [],
- };
-
- if (mode === "history") {
- historyMode();
- } else {
- hashMode();
- }
-
- this.getMode = function setMode() {
- return mode;
- };
-
- // 返回上一页
- this.back = function back() {
- var backCount = -1;
- if (mode === "history") {
- backCount = history.state && history.state._is === "first" ? -2 : -1;
- } else {
- backCount = location.hash === "#goback" ? -2 : -1;
- }
- history.go(backCount);
- };
-
- // 停留
- this.stay = function stay() {
- history.forward();
- };
-
- // 监听
- this.on = function on(key, cb) {
- if (typeof key !== "string") {
- return;
- }
- if (typeof cb !== "function") {
- return;
- }
- var method = methods[key];
- method.push(cb);
- };
-
- // 取消监听
- this.off = function off(key, cb) {
- if (typeof key !== "string") {
- return;
- }
- if (typeof cb !== "function") {
- return;
- }
- var method = methods[key] || [];
- for (var i = 0, l = method.length; i < l; i++) {
- if (method[i] === cb) {
- method.splice(i, 1);
- break;
- }
- }
- };
-
- function historyMode() {
- // history 模式
- setTimeout(function () {
- if (history.state === null || history.state._is !== "first") {
- history.pushState({ _is: "first" }, "", location.href);
- }
- }, 300);
-
- window.addEventListener("popstate", function (e) {
- if (history.state === null || history.state._is !== "first") {
- var method = methods.goback || [];
- for (var i = 0, l = method.length; i < l; i++) {
- method[i].call(self);
- }
- }
- });
- }
-
- function hashMode() {
- // hash模式
- setTimeout(function () {
- if ("#goback" !== location.hash) {
- location.hash = "goback";
- }
- }, 300);
-
- window.addEventListener("hashchange", function (e) {
- // 判断存在hash值才进入
- if ("#goback" !== location.hash) {
- var method = methods.goback || [];
- for (var i = 0, l = method.length; i < l; i++) {
- method[i].call(self);
- }
- }
- });
- }
- }
-
- var monBack = new MonitorBack({ mode: "history" });
-
- monBack.on("goback", function () {
- var r = confirm("是否退出");
- if (r == true) {
- this.back();
- } else {
- this.stay();
- }
- });

- ❤️爱心三连击1.看到这里了就点个在看支持下吧,你的「点赞,在看」是我创作的动力。
- 2.关注公众号程序员成长指北,回复「1」加入Node进阶交流群!「在这里有好多 Node 开发者,会讨论 Node 知识,互相学习」!
- 3.也可添加微信【ikoala520】,一起成长。
-
-
-
- “在看转发”是最大的支持
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。