实现vue无限滚动指令

2,105 阅读3分钟

概述

日常的开发当中,为了处理大量数据的情况,一般前端会采用分页展示,可以通过分页插件进行数据的按需分页请求展示,另一种解决大量数据的渲染的方式就是无限滚动,在移动端比较常见,也就是我们常见的滚动到底部加载更多数据,一般web端用下拉加载更多场景不是很多,但是也还是有,比如京东和淘宝的web官方,就用到了无限滚动,通过滚动到底部,然后加载更多数据。总之,无限滚动和分页插件都是为了解决大数据展示的问题,现在介绍下vue中通过自定义指令实现无限滚动。

最终效果

动画.gif

实现原理

在开始敲代码之前,先讲一下无限滚动的原理,首选我们需要之前的,怎么才算滚动到底部,然后我们才能去执行加载更多的函数。

关于高度计算的几个方法

clientHeigt

  • 这两个属性用于获取元素块可视区的宽高,该属性包括内边距 padding,但不包括边框 border外边距 margin垂直滚动条 image.png

scrollHeight

  • 一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容,也就是一个元素宽的实际高度,包含被滚动条卷走的部分。具体看下图:

scrollheight.png

scrollTop

  • 滚动条卷走的高度。,参考下图:

image.png

综上

得出滚动条到达底部的计算公式为:clientHeight + scrollTop == scrollHeight,知道这个之后,我们写逻辑就容易多了,只需要在滚动条到达底部的时候,重新取获取数据就可以了。

目录结构

image.png

App.vue

无限滚动首选需要一个固定高度的盒子然后设置 style="overflow: auto" 然后可以根据需要加上滚动结束的限制,比如loading等

<template>
  <div id="app">
  //外层包裹盒子
      <div
        class="infinite-list"
        v-infinite-scroll.loading.complated.immediate="load"
        style="overflow: auto"
        ref="infiniteList"
      >
        <ul>
          <li v-for="i in count" class="infinite-list-item">{{ i }}</li>
        </ul>
        //加载中
        <p v-if="loading && !complated" class="text">加载中...</p>
        //结束了
        <p v-if="complated" class="text">没有更多了</p>
      </div>
    </div>
  </div>
</template>

<script>
import Velocity from "velocity-animate";
import { DatePicker } from "./components/DatePicker/index";
export default {
  name: "App",
  components: {
    DatePicker,
  },
  data() {
      count: 1,
      loading: false,
      complated: false,
    };
  },
  methods: {
  //滚动到底部的处理逻辑
    load() {
      // 以下是定时器模拟异步数据请求,可根据自己的需求进行变更
      this.loading = true;
      setTimeout(() => {
        if (this.count >= 15) {
          this.complated = true;
          return;
        }
        this.count += 3;
        this.loading = false;
      }, 1000);
    },
  },
};
</script>

<style lang="less">
#app {
  .infinite-list {
    height: 300px;
    width: 500px;
    border: 1px solid red;
    li {
      height: 50px;
      background: #e8f3fe;
      margin: 10px;
      color: #7dbcfc;
      text-align: center;
      line-height: 50px;
    }
    .text {
      color: green;
      text-align: center;
      line-height: 50px;
    }
  }
}
</style>

./components/v-infinite-scroll/index.js

import { checkArriveBottom } from "./utils";
export default {
  install(Vue) {
    Vue.directive("infinite-scroll", {
      // 指令在插入的时候会执行一次
      inserted: function (el, binding, vnode) {
        const fn = binding.value;
        const context = vnode.context;
        let timer = null;
        // 指令的值必须是一个函数,我们好执行回调
        if (typeof fn != "function") {
          throw new Error("指令value必须为函数");
        }
        // 事件处理函数
        function handleScroll() {
          // 判断滚动条到达底部了,才开始执行回调
          if (checkArriveBottom(el)) {
            // 执行回调的时候,要把this指向组件实例
            fn.bind(context)();
          }
        }
        // 将滚动处理函数挂载到对应组件实例上面,便于组件更新的时候,对设置了loading和complate属性进行移除事件绑定
        context.handleScroll = handleScroll;
        // 如果设置有immediate说明立即执行,则立即执行回调,直到将内容撑满内容区
        if (binding?.modifiers?.immediate) {
          timer = setInterval(() => {
            // 子元素的总高度大于设置指令的父级包裹元素就表示填满了可视区,停止加载
            const childScrollHeight = el.firstElementChild.scrollHeight;
            if (childScrollHeight >= el.clientHeight) {
              return clearInterval(timer);
            }
            handleScroll();
          }, 1500);
        }
        // 绑定滚动处理函数
        el.addEventListener("scroll", context.handleScroll);
      },
      //   组件更新的时候,会不断触发(最明显就是data中的响应式数据变化,会继续执行update方法)
      update(el, binding, vnode) {
        const context = vnode.context;
        // 如果加载中或者已经加载完了,就移除滚动事件
        if (
          (binding?.modifiers?.complated && context.complated) ||
          (binding?.modifiers?.loading && context.loading)
        ) {
          el.removeEventListener("scroll", context.handleScroll);
        } else {
          // 当loading和complate都是false的时候,表示可以继续加载
          el.addEventListener("scroll", context.handleScroll);
        }
      },
    });
  },
};

./components/v-infinite-scroll/utils.js

/**
 * @Description 用于判断滚动条是否到达底部
 * @param { Element }
 * @return { Boolean }
 **/
export function checkArriveBottom(el) {
  const clientHeight = el.clientHeight;
  const scrollTop = el.scrollTop;
  const scrollHeight = el.scrollHeight;
  //可以设置>=就行,这里也可以设置距离底部一定距离,自定义,不一定非要到达底部
  return clientHeight + scrollTop >= scrollHeight;
}

./components/v-infinite-scroll/main.js

import Vue from "vue";
import App from "./App.vue";
import vInfiniteScroll from "./components/v-infinite-scroll";
Vue.use(vInfiniteScroll);
Vue.use(myUi);
new Vue({
  render: (h) => h(App),
}).$mount("#app");

总结

完成上述指令,需要先阅读官网自定义指令文档,搞懂具体指令的一些钩子函数的用途以及触发时机,还有就是参数的意义,链接放这里cn.vuejs.org/v2/guide/cu…