Vue项目实践:使用滚动下拉分页优化大数据展示页面
前言
传统的分页机制通过点击页码来加载更多内容,虽然直观,但在处理大量数据时可能会导致用户体验不佳。相比之下,滚动下拉分页能够在用户滚动到页面底部时自动加载更多内容,既节省了用户操作,也使得数据的展示更加流畅自然。
准备工作
scrollTop,clientHeight,scrollHeight 详解
scrollTop
定义:scrollTop
属性表示元素在垂直方向上已经滚动过的距离。换句话说,它衡量的是元素内容顶部与视口顶部之间的距离。这个值通常是非负的,单位是像素。
应用场景:当你需要知道元素内部滚动了多少或者想要手动设置滚动位置时,会用到这个属性。例如,实现“回到顶部”按钮功能时,可以设置scrollTop
为0
来瞬间返回顶部。clientHeight
定义:clientHeight
表示元素可视区域的高度,即元素内容区的高度加上垂直边框和内边距(padding
),但不包括水平滚动条(如果存在)、外边距或滚动条本身。这个值总是正的,单位也是像素。
应用场景:当你需要计算元素实际显示给用户的高度时会用到clientHeight
,比如判断元素是否完全在视口内显示。scrollHeight
定义:scrollHeight
表示元素的总高度,包括不可见的部分,也就是元素内容的总高度,不论内容是否在当前视口内可见。这包括了所有内边距、边框,但不包括外边距。对于没有滚动条的元素,scrollHeight
等于元素的clientHeight
。
应用场景:当你需要了解元素内容的完整高度,特别是用于判断是否还有更多内容可以滚动查看时,scrollHeight
就显得非常重要。比如,结合scrollTop
和clientHeight
来判断是否已经滚动到底部,从而实现无限滚动加载功能。
实现思路
在实现一个无限滚动(滚动加载更多)功能时,可以通过比较 scrollTop + clientHeight
和 scrollHeight
来判断是否接近滚动底部。
实现
效果
HTML\CSS结构
注意:一定要设置需要滚动盒子device-content
的高度,及其overflow: hidden; overflow-y: auto;
样式属性,使其具有滚动。通过handleScroll
进行监听父盒子的滚动状态,其中device-box
是父盒子中的内容列表。
<a-spin dot :loading="deviceLoading" tip="正在加载..."><div class="device-content" @scroll="handleScroll"><div v-for="device in deviceList" :key="device.id" class="device-box"><div class="device-con-box"><div class="device-con-img"></div><div class="device-con-text"><div class="device-con-text-top">{{device.detectDeviceFullName}}</div><div class="device-con-text-bottom"><spanv-if="device.runStatusKey === 'device_run_status:offline'"class="offline">离线</span><spanv-if="device.runStatusKey === 'device_run_status:working'"class="working">工作</span><spanv-if="device.runStatusKey === 'device_run_status:waiting'"class="waiting">空闲</span><spanv-if="device.runStatusKey === 'device_run_status:breakdown'"class="breakdown">故障</span></div></div></div></div></div>
</a-spin>
CSS
结构:
.device-content {width: 100%;height: 645px;overflow: hidden;overflow-y: auto;.device-box {cursor: pointer;height: 148px;width: 400px;float: left;border-radius: 12px;border: 1px solid #ccc;margin-bottom: 6px;margin-right: 6px;.device-con-box {height: 98px;width: 305px;margin: 0px auto;margin: 20px 68px 37px 48px;display: flex;.device-con-img {height: 80px;width: 80px;background: url('../../assets/images/deviceImg.png');}.device-con-text {width: 220px;height: 100%;padding-left: 16px;.device-con-text-top {height: 68px;width: 120%;padding-top: 23px;font-size: 14px;font-weight: 400;color: #000;}.device-con-text-bottom {height: 30px;width: 100%;font-family: Microsoft YaHei UI;font-size: 24px;font-weight: 700;line-height: 22px;text-align: left;color: #19cd61;}.line-division {margin-left: 10px;margin-right: 10px;color: #e7e7e7;font-size: 18px;position: relative;top: -3px;}}}}
}
监听父盒子函数handleScroll
检查用户是否已经滚动到容器的底部,如果滚动到了底部,再次判断页面的数据是否大于总条数,如果小于总条数,则继续进行加载下一页,如果大于总条数,则不进行加载。【距离顶部的距离+可视区高度>=元素总高度
】
// eslint-disable-next-line no-use-before-define
const handleScroll = (e: Event) => {const target = e.target as HTMLElement;// 检查用户是否已滚动到容器底部附近if (target.scrollTop + target.clientHeight >= target.scrollHeight - 10) {// 此条件检查是否还有更多页面的数据需要加载if (pagination.current * pagination.pageSize < pagination.total) {fetchData({ ...basePagination, current: pagination.current + 1 });}}
}
但是会出现,当触底的时候,多次请求分页列表,导致页面存储的数据大于数据库中数据的总条数。
处理以上触底时多次请求有两种方式可解决
使用防抖技术
使用防抖技术,确保在短时间内只处理一次滚动事件。
// 防抖函数
function debounce(func: any, wait: any) {let timeout: number | undefined;// eslint-disable-next-line func-namesreturn function (...args: any[]) {// eslint-disable-next-line @typescript-eslint/no-this-aliasconst context: any = this;clearTimeout(timeout);timeout = window.setTimeout(() => {func.apply(context, args);}, wait);};
}
添加标志位
添加一个标志位isFetching
来防止在请求完成之前再次发送请求。
const isFetching = ref(false);const fetchData = async (params: PolicyParams = { current: 1, pageSize: 20 }
) => {if (isFetching.value) return;isFetching.value = true;setLoading(true);try {const { data } = await getDetectDeviceMonitorPage(params);pagination.current = params.current;pagination.total = data.total;if (params.current === 1) {deviceList.value = data.data;} else {deviceList.value = [...deviceList.value, ...data.data];}} catch (err) {// you can report use errorHandler or other} finally {setLoading(false);isFetching.value = false;}
};const handleScroll = (e: Event) => {const target = e.target as HTMLElement;// 检查用户是否已滚动到容器底部附近if (target.scrollTop + target.clientHeight >= target.scrollHeight - 10) {// 此条件检查是否还有更多页面的数据需要加载if (!isFetching.value && pagination.current * pagination.pageSize < pagination.total) {fetchData({ ...basePagination, current: pagination.current + 1 });}}
};
通过添加防抖和标志位后的效果
完美解决触底出现多次分页请求。