vue 组件的 scrollBehavior

4,729 阅读4分钟

浏览器对用户访问网页的记录

  • 浏览器历史记录是对用户所访问的页面按时间顺序进行的记录和保存,以上是MDN对浏览器就如何跟踪用户访问过网页的解释性说明。
  • 通常我们很少会对页面回退或前进进行操作,在浏览器用户界面上提供有前进、回退按钮,页面跳转到离开页面之前的位置,而不是重新刷新页面,这个功能是由浏览器引擎(与渲染引擎、解析引擎概念不同)来完成的。当用户进入一个页面的时候,会往 history 栈中放入当前的记录,对页面级别的操作通过操作内置对象 history 可以满足一些需求。

vue对访问记录的管理

  • 进入正题,vue 路由跳转就是通过对 history.pushState()history.replaceState() 方法的模拟来实现,会往 history 栈中存放一条记录,这也是为什么 vuerouter.push 方法只能在支持 history.pushState() 方法的浏览器中使用,当调用 router.go() 或者 router.back() 方法的时候就和 history.go()history.back() 效果一样,都是对 history 栈中的记录进行访问,上述行为与通过浏览器的回退和前进效果也是一样。 但是,在不加处理的情况下,组件的滚动行为会跟我们想象的不同。

如何管理组件的滚动行为

  • 如果你是想简单粗暴的在每次切换组件的时候让页面回到顶部,router.beforeEach() 导航守卫会是一个不错的选择:

router.beforeEach((to, from, next) => {
        // 让页面回到顶部
        document.documentElement.scrollTop = 0
        // 一定不要忘记调用 next()
        next()
    })

但这不是我们的主题,要借助 vue-router 提供的 scrollBehavior,来管理组件滚动行为。

  • 关于 scrollBehavior,这里贴出官网对概念的介绍当然scrollBehavior借助 scrollBehavior,你也能让页面在组件切换的时候回到顶部:
 const scrollBehavior = function (to, from, savedPosition) {
        // savedPosition 会在你使用浏览器前进或后退按钮时候生效
       // 这个跟你使用 router.go() 或 router.back() 效果一致
       // 这也是为什么我在 tab 栏结构中放入了一个 点击回退 的按钮
       if (savedPosition) {
            return savedPosition
          } else {
            // 如果不是通过上述行为切换组件,就会让页面回到顶部
            return {x: 0, y: 0}
        }
    }

创建一个Router实例

注意:这个功能只在支持 history.pushState 的浏览器中可用。

当创建一个Router实例,你可以提供一个scrollBehavior方法

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
    // to:要进入的目标路由对象,到哪里去ss
    console.log(to)
    // from:离开的路由对象,从哪儿来
    console.log(from)  
    // savedPosition: 会记录滚动条的坐标,点击"后退/前进" 时的记录值(x:?,y:?)
    console.log(savedPosition)  
  }
})

scrollBehavior方法接收 to 和 from 路由对象。第三个参数 savedPosition当且仅当 popstate 导航(通过浏览器的 前进/后退 按钮触发)时才可用。

该方法返回滚动位置信息,如下所示:

  • { x: number, y: number }
  • { selector: string, offset? : { x: number, y: number }} (offset 只在 2.6.0+ 支持)

如果返回一个falsy值(falsy不是false,参考文档),或者是一个空对象,那么不会发生滚动。

举例:

scrollBehavior (to, from, savedPosition) {
  return { x: 0, y: 0 }
}

对于所有路由导航,简单的让页面滚动到顶部。

返回 savePosition ,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:

scrollBehavior (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}

如果你要模拟“滚动到锚点”的行为:

scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash
    }
  }
}

还可以利用路由元信息更细颗粒度的控制滚动。详细例子请移步这里

异步滚动

2.8.0 新增

scrollBehavior (to,from,savedPosition) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({x:0,y:0})
        },500)
    })
}

将其挂在到从页面级别的过渡组件的事件上,令其滚动行为和页面过渡行为一起良好运行是可能的。但是考虑到用例的多样性和复杂性,我们仅提供这个原始的借口,以支持不同用户场景的具体实现。

页面实战代码

<div id="app" class='wrapper'>
    <keep-alive>
        <!-- 需要缓存的视图组件 --> 
        <router-view v-if="$route.meta.keepAlive"></router-view>
     </keep-alive>
      <!-- 不需要缓存的视图组件 -->
     <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/home/Home.vue'
import Mvvm from "../views/Mvvm/index.vue";
import Transfer from "../views/Transfer/index.vue";
import Des from "../views/des/index.vue";
import Query from "../views/query";
import clipPath from "../views/clipPath";
import app from "../views/app/app.vue";


const routes = [{
    path: '/',
    name: 'Home',
    component: Home,
    children: [{
            path: '/mvvm',
            name: 'mvvm',
            component: Mvvm,
            meta: {
                keepAlive: true,
                title: ''
            }

        },
        {
            path: '/transfer',
            name: 'transfer',
            component: Transfer,
            meta: {
                keepAlive: true,
                title: ''
            }

        },
        {
            path: '/des',
            name: 'des',
            component: Des,
            meta: {
                keepAlive: true,

            }
        },
        {
            path: '/query',
            name: 'query',
            component: Query,
            meta: {
                keepAlive: true,
            }

        },
        {
            path: '/clipPath',
            name: 'clipPath',
            component: clipPath,
            meta: {
                keepAlive: true,
            }

        },
        {
            path: '/app',
            name: 'app',
            component: app,
            meta: {
                keepAlive: true,
            }

        }
    ]


}]
Vue.use(VueRouter)
const router = new VueRouter({
    mode: 'history',
    routes,
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
            console.log(savedPosition, 'savedPosition');
            return savedPosition
        } else {
            return { x: 0, y: 0 }
        }
    }
})

export default router