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">
<span
v-if="device.runStatusKey === 'device_run_status:offline'"
class="offline"
>离线</span
>
<span
v-if="device.runStatusKey === 'device_run_status:working'"
class="working"
>工作</span
>
<span
v-if="device.runStatusKey === 'device_run_status:waiting'"
class="waiting"
>空闲</span
>
<span
v-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-names
return function (...args: any[]) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const 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 });
}
}
};
通过添加防抖和标志位后的效果
完美解决触底出现多次分页请求。