在之前的1.0版本的基础上,添加滚动回收策略,保持页面只保留固定数量的数据,优化渲染
否则一直往数组里push内容,当数组长度过大,可能页面会崩溃(本需求是日志列表,由于日志数据可能会非常庞大,所以添加回收策略,如果列表数据不会很大,可以不用回收)
1.0版本代码如下:
vue3——利用自定义指令实现下拉框分页懒加载_vue3 el-select懒加载-CSDN博客
关于clientHeight、scrollTop和scrollHeight的关系
因此,可以利用这个关系,写出滚动条滚动到最上面和最下面的距离,
最上面:DOM.scrollTop + DOM.clientHeight == DOM.scrollHeight
最下面:DOM.scrollTop==0
但是为了更流畅的用户体验,可以让滚动条即将触顶或即将触底的时候就触发回调,可以加个阈值。
在该实践中,一页数据为10条,保持页面的数据恒定在50条(5页数据),滚动条向下滑动触底时,若大于5页,在请求第六页的同时,回收第一页的内容;反之,滚动条向上滑动触顶时,重新请求被回收的数据,同时,回收数组最末尾的一页数据,以此类推,保持整个页面的DOM只渲染小于50条数据。
自定义指令代码如下:
(ps:相比1.0版本加了阈值,加了即将触顶的判断,还加了节流,防止多次重复触发)
//自定义指令:实现下拉框下拉到末尾时,加载下一页的内容
// 使用时传两个参数,一个是下拉框的class,一个是下拉框滚动到末尾时触发的函数,比如:
// v-el-select-loadmore="{
// selector: '.myOption .el-select-dropdown .el-select-dropdown__wrap',
// loadFunction: loadMore
// }"
import { scrollType } from '@/views/AdminConsole/logManage/tenantLog/tenantLogType'
export default {
mounted(el, binding) {
//解构传来的值
const {
value: { selector, loadFunction }
} = binding
const SELECTWRAP_DOM = document.querySelector(selector)
//滚动事件添加节流,性能优化
let topScrollTimeout = null
let bottomScrollTimeout = null
if (SELECTWRAP_DOM) {
const scrollHandler = () => {
//清空定时器
if (topScrollTimeout) {
clearTimeout(topScrollTimeout)
}
if (bottomScrollTimeout) {
clearTimeout(bottomScrollTimeout)
}
bottomScrollTimeout = setTimeout(() => {
const condition = SELECTWRAP_DOM.scrollTop + SELECTWRAP_DOM.clientHeight >= SELECTWRAP_DOM.scrollHeight - 2
//滚动条滚到最下面
if (condition) {
loadFunction(scrollType.bottom)
}
}, 300)
topScrollTimeout = setTimeout(() => {
const scrollTop = SELECTWRAP_DOM.scrollTop
const threshold = 2
//滚动到最上面
if (scrollTop <= threshold) {
loadFunction(scrollType.top)
}
}, 300)
}
//赋值,为了方便销毁
el.dom = SELECTWRAP_DOM
el.event = scrollHandler
//监听滚动事件
SELECTWRAP_DOM.addEventListener('scroll', scrollHandler)
}
},
beforeUnmount(el) {
if (el.dom && el.event) {
el.dom.removeEventListener('scroll', el.event)
}
}
}
index.vue
/**
* 滚动条触底的逻辑
*/
async function scrollToBottom() {
//滚动到最下面
startPage += 1
endPage += 1
//发请求,获得接口数据
bottomLoading.value = true
try {
await getLogListWrap(copySearchObj.value, endPage)
} catch (err) {
proxy.$errorHandle(err)
}
tableData.push(...itemList)
//删除原数组前10项
tableData.splice(0, 10)
}
/**
* 滚动条触顶的逻辑
*/
async function scrollToTop() {
startPage -= 1
endPage -= 1
//发请求,获得接口数据
topLoading.value = true
try {
await getLogListWrap(copySearchObj.value, startPage)
} catch (err) {
proxy.$errorHandle(err)
}
tableData.unshift(...itemList)
//如果是最后一页,删掉原数组最后的项数
if (endPage == totalPage - 1) {
let lastItem = totalCount % 10
tableData.splice(tableData.length - lastItem, lastItem)
} else {
//删掉原数组后10项
tableData.splice(tableData.length - 10, 10)
}
}
/**
* 性能优化:只保留50条数据,tableData在尾部增加数据的时候,删除头部的部分数据,反之也是
* @param flag 判断是滚动条是触底还是触顶
*/
async function loadMore(flag: String) {
const { clientHeight, scrollHeight } = timelineRef.value
let isBottom = flag === scrollType.bottom
if (isBottom) {
if (totalCount <= 50 || endPage == totalPage) {
return
}
scrollToBottom()
//让滚动条向上滚一些距离,方便下次继续触发
nextTick(() => {
timelineRef.value.scrollTo(0, scrollHeight - clientHeight - 10)
})
} else {
//滚动到最上面
if (totalCount <= 50 || startPage == 1) {
return
}
scrollToTop()
//让滚动条向下滚一些距离,方便下次继续触发
nextTick(() => {
timelineRef.value.scrollTo(0, 20)
})
}
}
ps:滚动条触顶或者触底时,必须要用scrollTo方法让滚动条向下或者向上走一点距离,否则滚动条的位置会保持不变,就不会触发下一次回调。因为回调触发的条件是触顶或者触底,而不是一直让滚动条呆在顶部或者底部,必须有接触的行为。