您的位置:首页 > Web前端 > HTML5

H5 history API 改造

2020-03-05 00:06 1076 查看

最近做了一个移动端hybrid项目,将H5应用嵌入到app中。

简单介绍:大部分逻辑实现都靠H5实现,包括实现后退功能。一个app内只有一个页面,页面主体分成上下两部分:header和content。所有页面"跳转"都通过js函数对这两部分进行替换实现,每个跳转都有唯一函数。app原声部分有后退键退出app功能,当H5部分无法继续后退(处于history中第一个或者说在history序号为0)时双击可退出应用。

在开发过程早期,我完全使用h5 的history api 进行后退操作。每次js实现"跳转"前,先使用pushState推入历史,state的结构相对简单:

{func:'xx',param:['x','x']}
再设置历史事件函数:
window.onpopstate=function(event){
if(event.state&&event.state.func&&window[event.state.func]&&typeof(window[event.state.func])=='function'){
window[event.state.func].apply({},param);
}
}
每个"跳转"动作通过直接调用各自的js函数来实现,在页面上点击元素"跳转"前,将要调用的js函数名称和参数放入state,然后推入历史。需要返回时直接使用history.back()或者history.go()来实现效果。

后来发现这样实现的限制太大,有以下几点:

1、在使用api的时候只能向前或向后跳跃指定的步数,不能指定跳跃到第index个历史。

2、不知道当前历史在history list中的序号,也无法向前或向后检索到history list 中的某个满足特定条件的历史。

3、在应用中点击链接跳转到其他网站,返回后页面展示应用中主页的内容,但是当前历史很可能不在第一条,这样需要后退多次(每次都是主页内容)才能走到最早的历史,最后才能退出。

4、无法修改历史中state的数据,其中保存的函数参数如果已经过时(发生改变),我无法在跳转之前先修改这部分内容,这样的跳转功能不灵活。

5、只能获取history的总长度,这个数据对判断当前历史的序号一点帮助都没有,甚至想象不到这个数据的任何用处。


想骂一下H5这个history api的功能,真的比较残缺。但是功能全比不全好,有比没有好,有就不错了,要啥自行车。

想解决这些问题,好像没有办法了。

真的没有办法了吗?有时候事情看起来没法做,实际上做到的还是有不少的。


仔细想想,这个api里面我实际需要的东西只有一个,就是页面后退的回调事件,onpopstate。

浏览器把history 的list藏得这么深,老子不用你这个list了,自己维护一个list。

只需要在每次需要的时候能触发onpopstate事件,剩下的判断可以自己实现。

我可以每次都通过window.history.back()来触发onpopstate,在onpopstate中pushState就能让history恢复之前的序号。这样就能一直触发onpopstate。

所有js端调用history的api,创建一个对象来代理history,叫myHistory。

myHistory结构如下:

{list:[],
  currentIndex:0,//当前历史序号
  ongoingFlag:false,//代理history操作标志
  pushState:function(){
    
  },replaceState:function(){
    
  },go:function(){
    
  },back:function(){
    
  }popstate:function(){//在onpopstate中判断如果是myHistory触发事件,调用此方法实现"跳转"效果
    var state=this.list[this.currentIndex].state;
    var func=state.func;
    var param=state.param;
    if(func&&window[func]&&'function'==typeof(window[func])){
      try{
        window[func].apply({},param);
      }catch(e){
      }
    }
  }
}
在代理的back、go中做三件事:

1、设置ongoingFlag=true,表示当前myHistory正在使用history api,用来和前进后退键进行区分。

2、重新定位myHistory 列表的当前位置。

3、window.history.back(),这会触发onpopstate。

在代理的pushState中做两件事:

1、判断当前历史在myHistory列表中的序号是否为0,如果是,执行history.pushState(),此时前进键灰化。

2、在myHistory列表的当前项后插入新state对象,并抛弃后面的所有state。

在onpopstate中先进行判断,是代理myHistory操作还是前进后退键操作(上面的ongoingFlag):

1、如果ongoingFlag=false,是前进后退键,全部当作后退处理(前进和后退无法分辨,么有办法)。

    1)myHistory当前历史向前跳一步(currentIndex--;popstate();)。

    2)判断myHistory当前历史序号是否为0,如果不是,说明未返回到主页,应该执行history.pushState方法,给下次后退操作留下历史。

2、如果ongongFlag=true,是代理myHistory操作,当前历史已经重新定位,只需直接触发muHistory.popstate方法

    1)标志复位ongongFlag=false;

    2)直接调用myHistory.popstate();

    3)同1-2)。

这样改造后的效果是,我可以使用代理的历史对象来很方便地维护自己的历史,可以进行历史的修改、历史的特定条件搜索和指定序号的历史跳转。

这样修改后留下一个前进键的问题,虽然每次跳转后在onpopstate中基本都会发生pushstate的动作,但是后退到主页时是不能pushstate的,否则后退键需要多摁一次才能触发app的退出功能。此时前进键是不会灰化的,这时点击前进键,我们是当作后退来处理,这是个逻辑上的错误,然而不能直接解决。

function historyBack(){
window.stopForwFlag=true;
window.history.back();
}
解决这个问题也是有办法的,如果在onpopstate中history.pushState时推入自定义后退函数historyBack,并且在onpopstate函数前部进行state判断和执行,可以在点击前进键后触发自定义后退函数,执行history.back(),为了防止之后继续触发onpopstate中的逻辑,在historyBack函数中设置stopForwFlag=true,同时onpopsate函数最前部判断此值如果为true,设置成false后立即返回。

最后应用中在非主页时前进键灰化,在主页时前进键点击后立即后退,实现了一个不太完美但是更为灵活的历史api。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
hjmksxs 发布了1 篇原创文章 · 获赞 0 · 访问量 503 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: