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