概述

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

最终效果

实现原理

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

关于高度计算的几个方法

clientHeigt

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

scrollHeight

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

scrollTop

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

综上

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

目录结构

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…

到此这篇关于手写vue无限滚动指令的文章就介绍到这了,更多相关vue无限滚动指令内容请搜索阿兔在线工具以前的文章或继续浏览下面的相关文章希望大家以后多多支持阿兔在线工具!

点赞(0)

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部