从一个需求说起,H5实现万能返回

H5开发返回上一页是一个很辣手的问题。开发中,我们一般用history.go(-1)返回上一页,原生返回按钮的动作也是history.go(-1)。由于业务需求,某些页面返回需要go(-2)或go(-3)、go(-n),有些页面返回时还有加一个弹窗,让用户再次确认是否离开,这些都有监听物理机的返回事件。开发时,思维打结了,没想到“监听物理机的返回事件”这关键句子,踩了挺多坑的,本文就是记录一下小编的踩坑过程。

前几天就遇到了一个需求,离开正在编辑的页面时,弹窗提示用户,尚未保存编辑信息,是否确定离开。确定则离开,取消则留下继续编辑。当时觉得很简单,调用window.onbeforeunload便可,很快实现了这个需求。

beforeunload

// 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

由于所有的返回上一页都是调用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的兼容性,兼容性也还可以。有时候就这么神奇,突然脑袋又多了另一个想法,也可以添加历史记录不触发浏览器请求。

hash

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使用了两种路由实现方式,供用户选择。

history and hash

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】,一起成长。



“在看转发”是最大的支持
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值