点击浏览器前进/后退时页面不刷新的兼容解决方案

5,281 阅读4分钟

问题现象

在业务场景中会出现类似需求,一进入a页面就发起请求对用户身份进行判断,如果该用户不满足操作页面的条件,则给出弹窗提示后跳转到登录页面b。到b页面后,再点击浏览器后退按钮回到a页面,发现a页面并没有刷新reload执行js,导致并没有再对用户身份的判断给出提示。

点击浏览器前进/后退按钮没有重新执行js的原因

一、back-forward cache(往返缓存)

bfcache是为了让用户在使用浏览器前进/后退按钮时拥有更加流畅体验的一种策略。这是一种较强的缓存策略,在点击浏览器前进/后退时DOM,window,js对象,xhr都有可能被缓存,但不同浏览器的缓存策略不一致。直到用户关闭浏览器,缓存状态才会被清除。

pageShow

对于应用bfcache策略的浏览器,如果想在页面重新从缓存中加载后定义一些行为的,可以使用pageshow事件(IE不支持)。当一条会话历史记录被执行的时候将会触发页面显示(pageshow)事件,无论是否来自缓存。

pageShow事件与onload事件类似,一般来说

  • 在页面第一次加载时:会先后触发onload和pageShow;
  • 从其他页面点击浏览器返回按钮:onload不一定被触发(桌面端chrome会触发onload),pageShow大概率会被触发(Chrome, FireFox, Safari)

判断是否是来自缓存的页面:

  • e.persisted: 可以根据pageShow事件对象中的e.persisted判断该页面是否来自缓存中,返回值为bool,为true则表示来自缓存中的数据。但不同浏览器e.persisted的表现不同,仅依赖e.persisted是不可靠的。

  • window.performance.navigation.type === 2: performance.navigation对象的type字段有以下情况:

    • 0: 点击链接,书签和表单提交,或者脚本操作,或者在url中直接输入地址
    • 1: 点击刷新页面按钮或者通过Location.reload()方法
    • 2: 页面通过历史记录和前进后退访问
    • 255: 任何其他方式 但这种判断方式,是将被废弃的Navigation Timing Level 1标准中的window.performance.navigation对象才包含type字段。建议使用Navigation Timing Level 2中window.performance.getEntriesByType("navigation")[0].type判断
  • window.performance.getEntriesByType("navigation")[0].type === 'back_forward': 其中type字段有以下情况:

    • 'navigate': 点击链接,书签和表单提交,或者脚本操作,或者在url中直接输入地址
    • 'reload': 点击刷新页面按钮或者通过Location.reload()方法
    • 'back_forward': 页面通过历史记录和前进后退访问
    • 'prerender': 由prerender link导航而来

综上,可得到以下pageShow的监听,解决ios的bfcache问题

window.addEventListener('pageshow', function (event) {
  if (event.persisted || (window.performance && window.performance.getEntriesByType && String(window.performance.getEntriesByType('navigation')[0].type) === 'back_forward')) 
  {
    location.reload();
  }
}

二、部分安卓浏览器的前进/后退不是使用bfcache策略加速页面展示

对于使用bfcache策略的浏览器可通过pageShow监听非初始化加载的操作,但有些安卓浏览器(如小米自带、uc、qq等部分版本浏览器)没有使用bfcache策略,而是将页面封装成类似webView的形式,对于这种情况,可通过visibilitychange事件判断是否为初始化加载。

  • visibilitychange可兼容safari以外的浏览器,在页面可见行发生变化时(如标签页切换显隐),会触发该事件

  • document.visibilityState有visible/hidden/prerender三种值

    • hidden:页面彻底不可见
    • visible:页面至少一部分可见
    • prerender:页面即将或正在渲染,处于不可见状态
  • 监听如下(出于兼容考虑,在window上挂载监听):

window.addEventListener("visibilitychange", function() {
  console.log(document.visibilityState);
  if(document.visibilityState == "hidden") {
      console.log(‘隐藏‘);
  } else if (document.visibilityState == "visible") {
      console.log(‘显示‘)
  }
});

三、总结

对于ios的bfcache可通过pageShow判断页面是否来源于缓存解决,而安卓端可通过监听visibilitychange事件判断页面当前可见性是否变化决定后续操作,基本可兼容大部分机型和场景。

四、通过其他方式避免缓存

cache-control

可对http的请求/响应头的Cache-Control设置禁止缓存:Cache-Control: no-cache, no-store,must-revalidate,强制从服务加载页面。但由于ios支持bfcache,该方法在ios无效,可满足大多数场景。

也可以使用meta标签, 有时chrome等浏览器不支持:

<META HTTP-EQUIV="pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate">
<META HTTP-EQUIV="expires" CONTENT="0">

随机数

对于请求的缓存,也可以通过随机数(如时间戳"?timestamp=" + new Date().getTime())避免缓存。