微信H5页面回退,定位到原位置做法

3,347 阅读2分钟

问题由来

  • 记上周做了一个微信 H5 的落地页,由于只有一个页面,而且全是图拼接而成的,所有采取了原始的 jQuery 写法
  • 内部有一些点击之后跳转到外部链接的需求,于是咔咔写下 :
 $('xxx').click(function(){
   localtion.href = url // 有埋点,不使用a链接
 })
  • 然后返回页面的时候,有趣的事情发生了!

微信内置浏览器在返回上一页面,且上一页面包含AJAX代码时(埋点需要AJAX)

  • IOS 默认是不进行页面刷新的,但 android页面就会被强制刷新
  • 那么就会出现两种情况,ios 微信返回上一页的原位置;而 Android 直接定位到了顶部,弱化了用户体验

尝试解决

  • Android 在返回页面后定位到原来位置

方案①滚动定位

  • 跳转之前记录当前页面的滚动位置,存到 sessionStorage 里面
 $.ajax({
   type:'POST',
   url:`${baseUrl}/app/sta`,
   data:{ requestId,type},
   success(res){
     if(res.code===200){
       // 跳转之前先记录当前的滚动位置
       sessionStorage.setItem('scrollTop',$('.wrapper').scrollTop());
       location.href = hrefUrl
     }else{
       clearTimeout(timer)
       $('.model').stop().fadeIn(1000)
       timer = setTimeout(() => {
       $('.model').stop().fadeOut(700)
       }, 2000);
     }
   }
 })
  • 在返回的时候判断是否滚动的位置是否存在,然后直接滚动到对应位置,最后清除sessionStorage
 const u = navigator.userAgent
 const isAndroid = u.includes('Android') || u.includes('android') || u.includes('Adr')
 ​
 // 如果是安卓设备,且保存了滚动位置
 if(isAndroid && sessionStorage.getItem('scrollTop')){
   $('.wrapper').scrollTop(Number(sessionStorage.getItem('scrollTop')));
   // 清除上次缓存
   sessionStorage.removeItem('scrollTop');
 }
  • 然后发现这个做法定位根本不准确,页面每次点击去总会往上跑,有时候甚至相差很大(几百 px

image-202211161535282523.gif

  • 因为此时图片的父盒子高度是由图片撑开的,而图片又做了懒加载,
  • 然后我想是不是页面没给具体高度,返回时页面未加载完成,无法获取具体高度,所以导致定位不准
  • 随后给根标签设置了一个 height:4091px,让 html 滚动,在浏览器确实获取到了 document.documentElement.scrollTop ,但是打开手机的调试工具 vconsloe,发现document.documentElement.scrollTop 始终为0,而且 document.documentElement 是个空对象
  • 而且给跟标签设置了高度,首次进入页面会发现滑动不下去,多滑动几次才行

方案②锚点定位

  • 采取锚点定位的方法,跳转之前先记录点击的锚点
 $('.reserve').click(function(){
     sessionStorage.setItem('anchor', '#knowledge');
     ...
   })
 })
  • 返回页面时检测是否存在锚点,然后跳转到锚点位置
 if(isAndroid){
   location.href = sessionStorage.getItem('anchor');
   // 清除上次缓存
   sessionStorage.removeItem('anchor');
 }
  • 这样做虽然不是定位到原位置,但是返回之后能让用户看到点击的区域,也算是优化体验
  • 但是这样相当于返回原页面之后做了一次跳转
  • http://127.0.0.1:5502/index.html 跳转到 http://127.0.0.1:5502/index.html#knowledge
  • 于是浏览器存在了两条历史记录,安卓设备点击物理返回键的时候,会从带#knowledge的页面返回不带的页面,相当于需要点击两次才能退出页面,存在问题

方案③scrollIntoView

  • 滚动元素的父容器,使被调用 scrollIntoView() 的元素对用户可见
  • 效果和锚点差不多,只是不会浏览器不会产生记录
  • 点击前仍要保存点击位置的锚点
 if(isAndroid){
   $(sessionStorage.getItem('anchor'))[0].scrollIntoView()
   // 清除上次缓存
   sessionStorage.removeItem('anchor');
 }

可选参数

  • alignToTop(布尔值):

    • 如果为true,元素的顶端将和其所在滚动区的可视区域的顶端对齐
    • 如果为false,元素的底端将和其所在滚动区的可视区域的底端对齐
  • scrollIntoViewOptions(对象):

    • behavior: 定义动画过渡效果,autosmooth 之一,默认为 auto
    • block: 定义垂直方向的对齐, startcenterend,或 nearest之一,默认为 start
    • inline: 定义水平方向的对齐,startcenterend,或 nearest之一,默认为 nearest

注意:

alignToToptrue 相当于 scrollIntoViewOptions:{block:"start", inline:"nearest"}

alignToTopfalse 相当于 scrollIntoViewOptions:{block: "end", inline: "nearest"}

兼容性

image-20221116161954901.png

  • 苹果浏览器不兼容,但是不需要考虑

判断页面加载类型

  • 定位问题已经解决,但是还有一个问题!
  • 如果用户跳到外部链接后,直接把整个页面或者微信关掉,而不是返回首页,那么记录的锚点就不会被清除
  • 那么下次进入页面的时候,就会直接跳转到对应的位置,存在 bug
  • 这时就需要做一次判断,判断是以什么方式进入页面的 (直接进入 or 回退)
 $(document).ready(function (){
   window.historyPageMark = false; // 标记是否为历史页面 
   // 监听pageshow事件,页面显示则触发
   function onPageBack(callback){ 
     $(window).on("pageshow",e => { 
       if(window.historyPageMark === true || e.persisted || isEnterByBack()){ 
         // 如果是历史页面,则触发回调
         callback(); 
       } 
       window.historyPageMark = true; // 首次pageshow执行后更新标记为历史页面 
     }) 
   }
 ​
   function isEnterByBack(){ // 判断页面进入类型是否为回退 
     if(!window.performance) return false; 
     if(window.performance.getEntriesByType("navigation")[0]){
       return window.performance.getEntriesByType("navigation")[0].type === "back_forward"; 
     }
     if(window.performance.navigation){
       return window.performance.navigation.type === 2;
     }
     return false; 
   }
 })
  • window.performance: 用于测量网页Web 应用程序的性能

  • window.performance.getEntriesByType(): 返回一个 PerformanceEntry 对象的数组

  • window.performance.getEntriesByType("navigation")[0] :一般用于监控网页的性能

    • 其中type属性有两个值,reload 表示重载,back_forward 表示回退

image-20221116164641502.png