使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 可以自定义路由切换时页面如何滚动。

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

1. 先给占位符加一层缓存

Home 是需要记住滚动条位置的组件

<!-- 路由占位符 -->
<keep-alive include="Home">
    <router-view></router-view>
</keep-alive>

2. 在路由上加一个源信息meta,记录滚动条的top值

const routes = [
  {
    path: '/home',
    component: Home,
    meta: {
      isRecord: true,
      top: 0
    }
  }
]

3. 使用 scrollBehavior

​ 在文档页面(http://localhost:8080/document)拉动滚动条,然后刷新浏览器会发现滚动条依然在原来的位置,这是浏览器的默认行为,会记录浏览器滚动条默认位置。

​ 但是点击浏览器“前进/后退”按钮,会发现当初那个页面的滚动条从0开始了,没有记录上一次滚动条的位置。现在要求点击浏览器“前进/后退”按钮,页面滚动条要记录上一次的位置,这时需要设置它的的滚动行为。

​ 这时候需要在路由配置中设置 scrollBehavior(to,from,savePosition)函数,函数有三个参数。scrollBehavior() 函数在点击浏览器的“前进/后退”,或者切换导航的时候触发

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

第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
在该方法内,可以通过判断路由to,from两个对象来做一些必要的判断;
savedPosition 参数是记录的上次滚动的位置;
通过return {x:number,y:number}来控制页面滚动的位置;

  • 对于所有路由导航,简单地让页面滚动到顶部。
scrollBehavior (to, from, savedPosition) {
    return{x:0, y:0}
}
  • 想要在后退时,滚动到上次滚动的位置,如果满足条件,savedPosition有值的情况下:
scrollBehavior (to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}
  • 新增情况:异步滚动

    项目中经常会遇到异步请求接口数据,这些异步操作通常会放在created、mounted钩子里面
    分析:通过在不同的生命周期钩子和scrollBehavior()使用console.log()打印顺序如下:

    • created钩子

    • mounted钩子

    • to参数: {} from参数: {}

    • 异步请求数据:get data

    • 打印位置信息 savedPosition {x: 0, y: 630}

      可知:必须使用异步滚动,否则,请求的数据重新赋值,将导致页面重新渲染

当页面数据需要请求加载有延迟的情况下,页面如果直接滚动,会出现滚动后,页面数据请求回来,DOM重新渲染,滚动失效的情况;

必须使用异步滚动,利用setTimeout跳出主线程将回调事件放到队列中。由于mouted比scrollBehavior函数早执行,所以异步请求的回调事件优先进入队列,接下去才是setTimeout的回调事件。根据队列 先进先出的原理。先执行了异步请求回调事件对data中的变量a做赋值操作。此时相当于这已经是个静态页面了,接着我只要执行return { x:0, y: 100 }。这样就已经触发了页面滚动到100px的效果。但是由于data数据发生改变,页面重新渲染又回到顶部。这时整个轻触滚动效果已经暗中执行完成,不会再出现遮罩层了。

官方文档给补充了异步滚动的方法:

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

这个会在返回后,有一定延迟再滚动,可以根据自己项目的具体情况进行一定修改,兼容;

  • 最终结果

    // 创建路由对象
    const router = new VueRouter({
      routes,
      scrollBehavior(to, from, savedPosition) {
        console.log(to.meta.top)
        // if (savedPosition) {
        //   return savedPosition
        // } else {
        //   return { x: 0, y: to.meta.top || 0 }
        // }
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            if (savedPosition) {
              resolve(savedPosition)
            } else {
              resolve({ x: 0, y: to.meta.top || 0 })
            }
          }, 100)
        })
      }
    })
    

4. 给页面添加事件监听

Home.vue

// 当组件第一次被创建的时候 先触发 created 再触发 activated
// 之后被激活的时候 触发 activated
activated() {
    fn = this.recordTopHandler()
    // 添加滚动的事件监听
    window.addEventListener('scroll', fn)
},
// 当组件被缓存的时候 触发 deactivated
deactivated() {
   // 移除事件监听
   window.removeEventListener('scroll', fn)
 }

fn 函数 用来记录滚动条的位置

  • 使用了一个防抖函数 _.debounce(func, [wait=0], [options=]),官方文档:lodash.debounce
  • _.debounce方法的作用是防抖动,当你的事件在不断触发的时候,会根据你设置的间隔时间只触发一次回调。也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。(空闲时间大于,设定的时间是才会执行!!!)
recordTopHandler() {
    return _.debounce(
        () => {
            this.$route.meta.top = window.scrollY
        },
        50,
        { trailing: true }
    )
}
Logo

前往低代码交流专区

更多推荐