在普通页面中,点击浏览器的返回按钮,在返回到上一页时会处在上次浏览的位置。单页面应用中,由于始终是同一个页面, 因此需要自行实现页面返回时的锚点。Vue-router的Scroll Behavior 可以用于解决这个问题,但是只能应用在HTML5 history模式 。本文实现了在hash模式下的锚点跳转。
 
锚点位置存储 Vue-router要求在HTML5 history模式下,是为了使用pushState、replaceState API以及popstate事件:
参考源码html5.js
 
1 2 3 4 5 6 7 8 9 push (location : RawLocation , onComplete?: Function , onAbort?: Function ) { const  { current : fromRoute } = this this .transitionTo (location, route  =>  {    pushState (cleanPath (this .base  + route.fullPath ))     handleScroll (this .router , route, fromRoute, false )     onComplete && onComplete (route) }, onAbort) } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export  function  pushState  (url?: string, replace?: boolean) {  saveScrollPosition ()         const  history = window .history    try  {     if  (replace) {       history.replaceState ({ key : _key }, '' , url)     } else  {       _key = genKey ()       history.pushState ({ key : _key }, '' , url)     }   } catch  (e) {     window .location [replace ? 'replace'  : 'assign' ](url)   } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export  function  setupScroll  () {    window .addEventListener ('popstate' , e  =>  {         saveScrollPosition ();         if  (e.state  && e.state .key ) {             setStateKey (e.state .key );         }     }) } window .addEventListener ('popstate' , e  =>  {    this .transitionTo (getLocation (this .base ), route  =>  {     if  (expectScroll) {         handleScroll (router, route, this .current , true )     }     }) }) 
 
在hash模式下需要我们自己记录锚点位置。可以维护一个与history相同的数组,每次页面跳转时在Vue-router提供的钩子函数中遍历数组,存储锚点位置。
锚点滚动 Vue-router本身提供了scrollBehavior方法,用来进行锚点跳转。但是该方法只能用在HTML5 history模式下。研究了一下其源码:vue-router/src/util/scroll.js ,发现也是使用window.scrollTo()来进行页面的滚动。重要的是设置滚动的时机,应当在下一个页面绘制完成后进行跳转(wait until re-render finishes before scrolling)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 router.app .$nextTick(() =>  {     let  position = getScrollPosition ();     const  shouldScroll = behavior (to, from , isPop ? position : null );     if  (!shouldScroll) {         return ;     }     const  isObject = typeof  shouldScroll === 'object' ;     if  (isObject && typeof  shouldScroll.selector  === 'string' ) {         const  el = document .querySelector (shouldScroll.selector );         if  (el) {             position = getElementPosition (el);         } else  if  (isValidPosition (shouldScroll)) {             position = normalizePosition (shouldScroll);         }     }     else  if  (isObject && isValidPosition (shouldScroll)) {         position = normalizePosition (shouldScroll);     }     if  (position) {         window .scrollTo (position.x , position.y );     } }) 
 
我们目前已经自行记录了锚点,因此可以在router中模仿一个跳转过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 router.beforeEach ((to, from , next ) =>  {     next ();          router.app .$nextTick(() =>  {                   if  (to.fullPath  === browserHistoryLast.path              && browserHistoryLast.pos ) {             document .body .scrollTop  = browserHistoryLast.pos ;         }         else  {             document .body .scrollTop  = 0 ;         }     }); }); 
 
在router的钩子函数中,调用next之后,在$nextTick中进行页面的滚动,即可达到和scrollBehavior相似的效果。
需要注意的是,各个页面的数据应当存储在vuex中,不能每次进入页面都发送请求(即使不锚点也应当这么做 )。否侧因为返回时页面还在请求数据,不能达到锚点的效果。
关于过场动画 如果页面跳转有过场动画存在,非常容易在锚点滚动时发生闪烁。尝试了几种方式,都没能达到很好效果。
尝试过的方法的思路在这里记录一下,这些方法都会有很大的抖动闪烁,根本原因还是页面跳转的时机不对:
返回到有存储pos的旧页面时,在onTransitionAfterEnter中将页面滚动到记录的位置。打开新页面时,在onTransitionBeforeStart中将页面滚动设置为0,确保新页面在顶部。 
vue-router的过渡动画使用的是absolute定位+transform。因此尝试了给页面设置top值来消除闪烁。在跳转前给当前页面设置与目标页面滚动位置相同的top值,在滚动结束后由于不再是absolute定位,top值不再生效,没有闪烁发生。在返回时,列表页会首先绝对定位到首页要滚动的位置(此时会有闪烁),之后直接跳转到首页。闪烁集中在返回过渡效果之前。 
 
其他问题 
全局mixin中不能写组件中的过渡钩子,如beforeRouteEnter等,会报错。 这个issue中说已经修复了mixins usage are fixed in fb32ccb , 但是还是用不了。
 
computed只有在vuex中的变量变化时,才会进行更新。import进来的值不行。
  在onTransitionBeforeStart修改变量,不会使from页面中的computed更新。如果想要在页面跳转时更新from页面的computed,需要在router的钩子函数中进行修改,在this.$nextTick中调用next()。