列表无限加载和优化

925 阅读3分钟

前言

个人学习的记录,方便后续复习,如果有错误或者更好的方法,希望看到的大佬指出

无限加载

传统的无线加载是监听scroll事件,通过一些计算,来判断是否已经拉到底部,然后调用接口,刷新列表
intersectionObserver,可以自动观察元素是否可见,用起来也非常简单let observer = new IntersectionObserver(callback, option);

intersectionObserver

阮一峰大佬的日志戳这里

参数

有两个参数callback和option

callback

当目标元素的可见性发生变化的时候,就是说看见和消失的时候,都会触发回调

entries

callback也有参数entries,他是一个数组,数组中的isIntersecting可以用来判断观察元素是否可见 其实到了这里就可以完成无限滚动了,其他的参数可以参考阮一峰的日志

开始

页尾栏

完成无限滚动,底部最好有一个页尾栏,这里就简单的意思一下,<div class="bottom"></div>这个就当成页尾栏

  <div>
    <u-list :items="items"></u-list>
    <div class="bottom"></div>
  </div>

自定义指令

之前的记录中也说了,为了保证methods中的函数都是纯粹的逻辑,不去处理DOM,可以将需要对DOM进行处理的东西委托给自定义指令处理,所以这里给页尾栏添加一个自定义指令
fetchNext是调用列表接口的函数,就不展示了

<div class="bottom" v-intersect="{ handler: fetchNext }"></div>

代码

由于比较简单,直接贴上完整代码,注意一下细节就行,

  • 观察元素不可见的时候不调用获取列表的函数
  • 在inserted钩子函数中观察元素,并且要注意给el一个属性,使其可以在unbind中也获取到observer,方便unbind钩子函数中停止观察
const intersect = {
  inserted(el, binding) {
    const value = binding.value
    const { handler, options = {} } = value;

    let observer = new IntersectionObserver((entries = [], options) => {
      if (!el._observer) return
      if (handler && el._observer.init) {
        const isIntersecting = Boolean(
          entries.find((entry) => entry.isIntersecting)
        )
        if (isIntersecting) {
          handler()
        }
      }
      el._observer.init = true
    })
    el._observer = { init: false, observer }
    observer.observe(el);
  },
  unbind(el) {
    if (!el._observe) return;
    //停止观察
    el._observe.observer.unobserve(el);
    delete el._observe;
  }
}

实际效果演示

黑色的那个DIV就是一个简陋的页尾栏,比较简陋,感兴趣的可以优化一下

长列表优化

但是这个时候会发现一个问题,就是随着列表的长度的增加,DOM节点也会增加(如下图),当DOM节点到达一定数量的时候,会发生卡顿,所以接下来就要对他进行优化了

组件

    <u-infinite-list #default="{ sliceItems }" :items="items" :item-height="80">
      <u-list :items="sliceItems"></u-list>
    </u-infinite-list>

使用UInfiniteList组件将需要渲染的列表通过slot在父组件中传入UList中去

详解

 <div class="x-infinite" ref="container" :style="{ padding: padding }">
    <slot :sliceItems="sliceItems"></slot>
  </div>

我们的目的就是将不显示的列表把他从list中删除,所以就要知道上/下拉的时候,有多少列表被隐藏了,所以需要知道当前下拉距离和窗口文档显示区的高度,还需要有一个过度的过程

  data() {
    return {
      buffer: 5, //优化用户体验,一个过度的作用
      scrollTop: 0, //document.body.scrollTop 网页被卷去的高
      viewportHeight: 0, //window.innerHeight返回窗口的文档显示区的高度
    };
  },

然后通过计算属性获得over(数组的开始索引) ,down(数组的结束索引),sliceItems(需要渲染的数组), padding(div样式用来减少div渲染列表的区域)这些数据

  computed: {
    over() {
      return Math.max(
        Math.ceil(this.scrollTop / this.itemHeight) - this.buffer,
        0
      );
    },
    down() {
      return Math.min(
        Math.ceil(
          (this.scrollTop + this.viewportHeight) / this.itemHeight + this.buffer![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9348d3494d9748918c329e1093893b44~tplv-k3u1fbpfcp-watermark.image)
        ),
        this.items.length
      );
    },
    sliceItems() {
      return this.items.slice(this.over, this.down);
    },
    padding() {
      return `${this.over * this.itemHeight}px 0 ${
        Math.max(this.items.length - this.down) * this.itemHeight
      }px 0`;
    },
  },

监听浏览器滚动

passive: true scroll时间的默认行为(页面滚动)是可以被preventDefault()组织的,所以他需要等监听器执行完毕,才能执行默认行为,,如果监听函数执行时间很长的话,就会出现卡顿,设置passive: true就可以是默认行为和监听函数一起执行
throttle节流函数,也可以减少对DOM的操作,对DOM进行一定的优化。

  created() {
    this.scrollTop = document.documentElement.scrollTop;
    this.viewportHeight = window.innerHeight;
    window.addEventListener("scroll", this.onScroll, {
      passive: true,
    });
  },
  destroyed() {
    window.removeEventListener("scroll", this.onScroll);
  },
  methods: {
    onScroll: throttle(function () {
      this.scrollTop = document.documentElement.scrollTop;
      this.viewportHeight = window.innerHeight;
    }),
  },

结束

到这里就结束了,感兴趣的可以自行试一下效果