vue程序员Vue

vue 组件的 scrollBehavior

2018-05-18  本文已影响1534人  一慢呀
浏览器对用户访问网页的记录
vue对访问记录的管理
vue组件滚动行为
  <ul class="tab">
      <li>
        <router-link to="/" >首页</router-link>
      </li>
      <li>
        <router-link to="/list" >列表</router-link>
      </li>
      <li>
        <router-link to="/about" >关于</router-link>
      </li>
      <li>
        <a href="#" @click='() => { this.$router.back() }'>点击回退</a>
      </li>
  </ul>
  <router-view></router-view>
  <!-- 以 home 组件为例 -->
  <ul class="list_content home">
     <li v-for='i in 10'>{{ i }}</li>
  </ul>

长这样:


请注意,现在开始滚动首页位置至第 5 屏的位置,当切换到列表以及关于页面的时候,会发现这两个页面的滚动行为和首页滚动行为一致。

既不涉及组件的缓存,也不涉及组件的复用,我们不禁会疑惑为什么首页的滚动会影响到其他两个页面,如果我们有当切换组件的时候,需要让当前组件的内容是从 scrollTop = 0 的时候开始浏览,那这样的结果将会是一个绊脚石。
原因如下,因为基于SPA模式开发,所以页面仅有一个,实现页面切换是利用哈希组件的映射关系,vue-router 是通过哈希来模拟完整的 url,但是对于页面来说仍是一个 url,所以在任何一个组件滚动页面,切换到其他组件的时候,页面仍保持滚动之前的状态,这就是出现上述现象的原因。
如何管理组件的滚动行为
    router.beforeEach((to, from, next) => {
        // 让页面回到顶部
        document.documentElement.scrollTop = 0
        // 一定不要忘记调用 next()
        next()
    })

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

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

这里用到路由的元信息 meta 更细颗粒度控制滚动行为,这里以 home 组件为例说明:

      const Home = {
          template: `
            <ul class="list_content home">
                <li v-for='i in 10'>{{ i }}</li>
            </ul>
            `,
          data () {
              return {
                  timerId: ''
              }
           },
          mounted () {
                 // 通过 addEventListener 方法注册事件的时候需要格外小心      
                // 如果在 destroyed 钩子函数中没有销毁 scroll 事件
                // 在激活 home 组件的时候会再次绑定 scroll 事件
                // window.addEventListener('scroll', this.justifyPos)
                // 通过 on 方式绑定事件能够有效避免上述情况
                window.onscroll = this.justifyPos
            },
          methods: {
                justifyPos () {
                    // 节流;
                    if (this.timerId) clearTimeout(this.timerId)
                    this.timerId = setTimeout(() => {
                        // 获取页面滚动距离之后设置给当前路由的 元信息
                        this.$route.meta.y = window.pageYOffset
                    }, 300)
                }
            },
          destroyed () {
                // 当组件销毁的时候,移除滚动行为监听, 清空定时器;
                // 该方法是绑定到 window 身上,即使跳转到其他组件,仍然会监听页面的滚动行为
                // window.removeEventListener('scroll', this.justifyPos)
                // clearTimeout(this.timerId)
            }
        }
        const List = {
            template: `
            <ul class="list_content list">
                <li v-for='i in 10'>{{ i }}</li>
            </ul>
            `
        }
        const About = {
            template: `
            <ul class="list_content about">
                <li v-for='i in 10'>{{ i }}</li>
            </ul>
            `
        }
        const routes = [
                // 设置 meta,细颗粒控制组件滚动
                {path: '/', component: Home, meta: {x: 0, y: 0}},
                {path: '/list', component: List, meta: {x: 0, y: 0}},
                {path: '/about', component: About, meta: {x: 0, y: 0}}
            ]
        const scrollBehavior = function (to, from, savedPosition) {
            return to.meta
        }
        const router = new VueRouter({
            routes,
            scrollBehavior,
            linkExactActiveClass: 'current'
        })

上述会在 home 组件滚动停止的时候记录当前组件的滚动位置信息,并且存储到对应 home 组件的路由 meta 这个对象中,当切换到 list 或者 about 页面之后在回到 home 组件,仍会保留着离开之前的位置,而不是简单地让页面回到顶部。
但是,你会发现你只是针对 home 组件的滚动行为进行控制,listabout 组件的滚动行为也能够实现个性化定制,即也会将当前组件的滚动行为记录在对应的路由 meta 中。
这会让人疑惑,因为在 listabout 组件中并没有设置 justifyPos 方法,并且 window.onscroll = this.justifyPosthis 绑定到当前的上下文中。
vue 官网对于组件销毁介绍,会解绑所有的指令以及事件监听,但是对于方法的引用处理没有提到,个人觉得在这里应该抛出警告或者错误的,但是 vue 却没有提示,这也是令我困惑的一点。但是,这却为滚动行为监听提供了更好的处理方法,那就是绑定到 vue 根实例上,而不是某一个单一组件上,因为 this 会自动绑定到当前上下文:

    new Vue({
        router,
        data: {
            timerId: ''
        },
        mounted () {
            window.addEventListener('scroll', this.justifyPos)
        },
        methods: {
            justifyPos () {
                if (this.timerId) clearTimeout(this.timerId)
                this.timerId = setTimeout(() => {
                    this.$route.meta.y = window.pageYOffset
                }, 300)
            }
        }
    }).$mount('#app')
当better-scroll(以下简称bs)遇上vue,如何定制滚动行为
    const Home = {
            template: `
            <div class="wrapper" ref="wrapper">
                <ul class="list_content home">
                    <li v-for='i in 10' @click='goList'>{{ i }}</li>
                </ul>
            </div>
            `,
            mounted () {
                this.$nextTick(() => {
                    // 初始化 BS
                    this._initScroll()
                    // 滚动监听
                    this.scroll.on('scrollEnd', (pos) => {
                        // 将滚动信息设置给当前路由元信息
                        this.$route.meta.y = pos.y
                    })
                    // 当前组件激活的时候,滚动到离开前位置
                    // 如果你想要滚动动画效果,可以在 scrollTo 方法中自定义
                    this.scroll.scrollTo(0, this.$route.meta.y, 0)
                })
            },
            methods: {
                _initScroll () {
                    if (!this.$refs.wrapper) return
                    this.scroll = new BScroll(this.$refs.wrapper, {
                        mouseWheel: {
                            speed: 20,
                            invert: false,
                            easeTime: 300
                        },
                        // 派发 click 事件;
                        click: true
                    })
                },
                // 跳转到列表页;
                goList () {
                    this.$router.push({name: 'list'})
                }
            }
        }
        const List = {
            template: `
            <ul class="list_content list">
                <li v-for='i in 10' @click='goHome'>{{ i }}</li>
            </ul>
            `,
            methods: {
                // 回跳到首页
                goHome () {
                    this.$router.push({name: 'home'})
                }
            }
        }
        const routes =  [
                // 设置 meta
                {path: '/', name: 'home', component: Home, meta: {x: 0, y: 0}},
                {path: '/list', name: 'list', component: List, meta: {x: 0, y: 0}},
                {path: '/about', component: About, meta: {x: 0, y: 0}}
            ]
        // scrollBehavior 其实这里已经没有什么作用了,因为当前组件的高度被定死和整个屏幕一样高
        // const scrollBehavior = function (to, from, savedPosition) {
        //    return to.meta
        // }
        // 设置路由
        const router = new VueRouter({
            routes,
            scrollBehavior,
            linkExactActiveClass: 'current'
        })
        // 挂载
        new Vue({router}).$mount('#app')
写在最后
上一篇 下一篇

猜你喜欢

热点阅读