开发一个不损耗丝毫滚动性能的上拉加载组件

308 阅读3分钟

上拉加载存在的一些问题

  • 滚动性能问题

  • 首屏数据不够一页,立马触发第二次上拉加载事件

  • 上拉加载数据的时候,连续性的触发事件

  • 页面往上滚动的时候,也触发上拉加载事件

这篇文章主要解决上拉加载滚动性能问题

造成性能损耗的原因

由于上拉加载场景需要在滚动的时候判断是否需要触发请求加载事件(当然你也可以通过轮询的方式去判断,但是这种方式是不推荐的),那么就需要在滚动列表的最底部安置一个空的元素作为标识符,滚动的时候判断这个标识符元素是否可见,来达到判断是否触发请求加载事件,这是目前社区常见的做法,这种做法造成性能损耗的原因有:

  • 滚动事件触发频率过高,一旦滚动事件里面有比较耗时的代码执行,就会造成失帧

  • 判断标识符元素是否可见,就要读取元素位置,会造成重绘甚至回流

    // 标识符元素
    let tagDOM = document.getElementById('tag')
    const winWidth = window.innerWidth
    const winHeight = window.innerHeight
    // 监听滚动
    scrollDOM.addEventListener('scroll', () => {
      let { top = 0, left = 0 } = tagDOM.getBoundingClientRect()
      // 判断元素是否可见
      if (left >= 0 && left <= winWidth && top >= 0 && top < winHeight) {
        // do......
      }
    }, { passive: true })
    

即使在滚动事件里面加入节流或者防抖函数还是避免不了重绘或者回流

解决问题

那么我们能不能想办法利用空闲的时间,去读取需要的数据,滚动的时候只需要判断数据而不用读取数据呢

实际上我们只需要在页面初始化完的时候读一次标识符元素的位置,每次上拉加载完数据并且已经渲染完成,再去读一次标识符元素的位置就可以,这样就不用在滚动事件里面去多次读取位置信息。

  • 在无框架项目里面,可以利用Mutation Observer API来监听元素渲染完成

    Mutation Observer API这里不做详细介绍,可以参考阮一峰 Mutation Observer API

  • vue项目

    // child.vue
    {
        data () {
            return {
                // 标识符位置
                modeReact: Object.freeze({}),
                // 标识符元素
                modeDOM: Object.freeze(null)
            }
        }
        mounted () {
          this.$nextTick(() => {
              this.modeDOM = Object.freeze(this.modeDOM || (this.$refs && this.$refs.mode))
              this.getModeRect()
              // 监听滚动事件
              this.scrollDOM.addEventListener('scroll', this.scrollFun, isSupportPassive() ? { passive: true } : false)
          })       
        },
        methods: {
            // 获取标识符的距离
            getModeRect () {
                if (!this.modeDOM) return false
                const { top = 0, left = 0 } = this.modeDOM.getBoundingClientRect() || {}
                this.modeReact = Object.freeze({
                    top: (this.scrollEl ? this.scrollEl.scrollTop : window.pageYOffset) + top,
                    left
                })
            },
            // 滚动事件
            scrollFun () {
                if (this.isLoading) return false
                let scrollTop = (this.scrollEl ? this.scrollEl.scrollTop : window.pageYOffset) || 0
                let preScrollTop = this.currentScrollTop
                this.currentScrollTop = Object.freeze(scrollTop)
                let { top, left } = this.modeReact
                if (top <= scrollTop + this.scrollHeight && left >= 0 && left <= this.wWidth && this.isHaveData) {
                this.isLoading = true
                this.$emit('loadMore', this.loadMoreCallback)
              }
            }
        }
    }
    
    // parent.vue
    <template>
        <child ref="child" @loadMore="loadMore"></child>
    </template>
    {
        methods: {
            loadMore () {
                do...... // 请求接口,接口请求完成赋值列表变量
                // 读取位置数据
                this.$nextTick(() => {
                    let child = this.$refs.child
                    if (child && child.getModeRect) child.getModeRect()
                })
            }
        }
    }
    

    这样就完成

    总结

    如果不考虑兼容性的话,可以直接使用IntersectionObserver API来监听元素的可见与不可见来处理

    最后插条招聘信息,上市公司,看重技术,招聘软件相关各岗位(前端,go,java,android,ios,php),季度考核,季度奖金最高2两个月薪资,最高年薪能发20薪,周六周天加班按正常一倍工资发放,晚上加班打车报销,晚上加班有餐补,上班不用打卡,有找工作换工作的,可以把简历发给我