🦉来来来,整个下拉加载(无限滚动)指令!

160 阅读3分钟

前言

开发中,无限滚动或加载更多功能非常常见,特别是在长列表或分页数据展示的场景中。
Vue项目开发时如果有一个无限滚动的自定义指令,那开发体验会好很多。
下面将详细介绍如何实现一个 load-more 指令,帮助用户滚动到页面底部时自动触发加载更多数据。

效果

701627b89e8b51511c04228f5f82f0da.gif

效果代码

<div
    style="width: 200px;height:300px;overflow:auto;border:1px solid #5e6d82;" 
    v-load-more="{ 
        distance: 100, 
        time: 500, 
        callback: addList, 
        lock: lock}" // lock 是个对象 {value:boolean}
>
        <wenps-button v-for="(item, index) in list" :key="index" class="mb10" style="width: 100%" theme='primary'>
            {{item.name}}
        </wenps-button>
    </div>

实现思路

1. 引入依赖

首先,我们需要引入一个节流函数 throttle,以防止在短时间内频繁触发加载更多操作。 节流函数可以通过第三方库或自定义实现,这里先用 lodash 演示一下。

import { throttle } from 'lodash'

2. 定义指令对象

我们定义一个 loadMore 对象,这个对象包含 bind 和 unbind 两个方法。bind 方法在指令第一次绑定到元素时调用,unbind 方法在指令从元素上解绑时调用。
bind执行时,需要对目标元素绑定滚动加载事件,在unbind执行时,移除目标元素绑定的滚动加载事件。

const loadMore = {
  bind (element, binding) {
    // 初始化配置参数
    // 添加滚动加载事件
  },
  unbind (element, binding) {
    // 移除滚动加载事件
  }
}

3. 初始化配置参数,目标元素绑定滚动加载事件

实现指令的核心能力:

  • 初始化默认参数,这里默认接受三个属性distance,time,callback,lock,分别是滚动触发默认高度,防抖时间,滚动触发回调以及锁。(lock 的作用是处理当回调异步时间大于防抖时间的情况)
  • 获取元素并创建一个节流的滚动事件处理器。该处理器会检查当前滚动位置是否接近底部,如果是,则调用用户提供的回调函数。
  • 注意:这里需要用节流,因为处理lock的情况下,lock的触发时间应该和节流时间重叠,如果是防抖的话就会变成lock时间加防抖时间,滚动回调禁用时间会变长。
const loadMore = {
      bind (element, binding) {
           // 初始化默认选项 
           let options = {
              distance: 0,
              time: 500,
              callback: () => {},
              lock: {
                  value: false
              }
            }
           // 合并默认选项和用户传递的选项 
           options = { ...options, ...binding.value } 
           // 获取绑定指令的元素 
           const el = element 
           // 定义一个防抖的滚动事件处理器 
           handler = throttle(() => { 
                   if (options.lock.value) return
                   // 获取元素的滚动位置、总高度和可见区域高度 
                   const { scrollTop, scrollHeight, clientHeight } = el 
                   // 判断是否滚动到了底部 
                   const isScrollEnd = scrollHeight - (scrollTop + clientHeight) <= (options.distance || 0) 
                   // 如果滚动到了底部且有回调函数,则调用回调函数 
                   if (isScrollEnd) { 
                           if (options && options.callback) { 
                                   options.callback() 
                           } 
                   } 
           }, options.time) 
      },
      unbind (element, binding) {
        // 移除滚动加载事件
      }
}

4.绑定移除目标元素的加载事件

给目标元素绑定滚动加载函数,值得注意的是,绑定之后还需要移除,但是移除时需要在原来的目标元素上removeEventListener,但是防抖的滚动事件处理器的作用范围在bind函数中,因此在unbind中无法获取这个滚动处理器。
所以我们可以直接把处理器直接绑定到目标元素上,unbind时就可以通过 目标元素得到处理器并解绑。

完整代码

因此,完整代码如下所示:

import { throttle } from 'lodash'

const loadMore = {
  bind (element, binding) {
    let options = {
      distance: 0,
      time: 500,
      callback: () => {},
      lock: {
        value: false
      }
    }
    options = { ...options, ...binding.value }
    const el = element
    // 滚动函数绑定到目标元素上
    el.handler = throttle(() => {
      if (options.lock.value) return
      const { scrollTop, scrollHeight, clientHeight } = el
      const isScrollEnd = scrollHeight - (scrollTop + clientHeight) <= (options.distance || 0)
      if (isScrollEnd) {
        if (options && options.callback) {
          options.callback()
        }
      }
    }, options.time)
    // 滚动监听
    el.addEventListener('scroll', el.handler)
  },
  unbind (el, binding) {
    // 移除滚动监听
    el.removeEventListener('scroll', el.handler)
  }
}