前端性能优化之虚拟列表

性能优化之虚拟列表

场景

尽管接口做了分页处理,但请求次数过多时,页面渲染的结构即长列表,会造成性能损耗。

描述

虚拟列表,就是让数据在固定个数的结构上渲染,结构不发生改变,而对应结构上的数据发生变化。

例如,设定列表容器容纳十个子组件,页面初始第一个子组件渲染的数据索引为0,当页面下滑一个子组件高度时,将第一个 子组件渲染的数据改为索引为1。

简而言之,获取的长列表数据可以十分大,几百、几千------

而虚拟列表只节选其中连续的十个,进行渲染展示。

技术背景

Vue3 Ts

页面结构

xml 复制代码
<div id="virtual-list--container" @scroll="vaScrollHandler" :style="{ 'height': listHeight }">
​
    <!-- 可视区 -->
    <div id="main-list--box" ref="visibleArea">
        <UserProfile :mode="0" v-for="(item, index) in fakeList" :key="index" :userInfo="item"></UserProfile>
    </div>
​
</div>
javascript 复制代码
/*
    在上述结构中,首先通过获取可视区的ref,获取它的容器高度,
    并除以虚拟列表总个数(10),得到每个子项的高度
*/
itemHeight.value = visibleArea.value?.clientHeight / 10
​
/*
    其中itemCount表示列表中显示出来的个数,
    比如8,表示列表总显示8个列表项,另2个总是由于滚动被遮住
*/
const listHeight = computed(() => itemCount * itemHeight.value + "px")     // 展示的列表高度

逻辑实现

在页面下滑过程中,如果距离底部还有一个列表项的高度,并且满足请求条件,则发起请求、添加数据。

接着,如果发生触底,那么就去节选所有数据allList中相应的十个,进行渲染fakeList

在页面下滑过程中,下滑了一个列表项的高度,其实就是虚拟列表结构整体渲染的数据的索引,都向后增加1位。

由于数据的更替,页面看起来就会闪烁,因此,此时需要做的就是通过scrollTop,纠正两个相同索引的数据,在前后两次页面渲染中的偏差。

对于向上滑动页面,同样的在触顶时更新渲染的数据fakeList,并修正偏差。

图示(来源掘金)

参数变量

scss 复制代码
const visibleArea = ref()
const itemHeight = ref(0)   // 单个item高度
const itemCount = 8    // 列表展示的个数8,而非当前页总数10
const listHeight = computed(() => itemCount * itemHeight.value + "px")     // 展示的列表高度
​
let startIndex = ref(0)
let endIndex = ref(9)
​
let isBottom = ref(false)           // 触底节流阀
let allowPreRequest = ref(true)    // 预加载阀
let initScrollTop = ref(0)
let hideCount = ref(0)          // 由于滚动隐藏的列表项个数
​
let allList = reactive<Object[]>([])    // 所有数据
let fakeList = reactive<Object[]>([])   // 虚拟列表渲染数据

滚动监听函数

ini 复制代码
const vaScrollHandler = (e: any) => {
    let { scrollTop, scrollHeight, clientHeight } = e.target
    
    /* 
        step4:触底或触顶时:更新触底变量,以使数据能刷新
        触底后修正了scrollTop,因此会进入这一层判断
    */
    if (scrollTop.toFixed(2) == itemHeight.value || scrollTop == 0) {
        isBottom.value = false
        allowPreRequest.value = true
    }
​
    // 判断滚动方向
    let direction = scrollTop - initScrollTop.value
    initScrollTop.value = scrollTop
    if (direction >= 0) {
        // 更新触底变量,否则每次只能触顶后才能触底更新
        isBottom.value = false
    } else {
        if (scrollTop == 0 && startIndex.value) {
            onReachTop()
        }
    }
​
    /* 
        step1:预加载下页数据(先)
        allowPreRequest节流阀用于由于scrollTop等参数的误差导致的重复进入判断
    */
    if (Math.ceil(scrollTop) + clientHeight >= Math.ceil(scrollHeight - itemHeight.value * 1) && allowPreRequest.value) {
        /*
            虚拟列表结束索引初始为9,每次触底都会更新endIndex的索引值(之后)
            如果结束索引endIndex 和 所有数据allList最后位元素的索引一致,满足更新条件,请求数据
        */
        if (endIndex.value == allList.length - 1) {
            mockRequest()
                .then((res: any) => {
                    res.forEach((item: any) => {
                        allList.push(item)
                    })
                    pageParam.pages++
                })
        }
        allowPreRequest.value = false
    }
​
​
    /*
        step2:触底判断(后)
        同样,isBottom节流阀用途一致
    */
    if (Math.ceil(scrollTop) + clientHeight == scrollHeight && !isBottom.value) {
        onReachBotoom()
        isBottom.value = true
    }
​
    /* 
        step3:触底后更新列表数据
    */
    function onReachBotoom() {
        hideCount.value = 10 - itemCount
​
        // 更新虚拟列表起始索引
        startIndex.value += hideCount.value - 1
        endIndex.value += hideCount.value - 1
​
​
        for (let i = 0; i < fakeList.length; i++) {
            /*
                这里是预防下滑速度过快,尽管成功发起了请求,
                但触底更新时数据仍不存在,导致页面列表项展示undefined
            */ 
            if (allList[i + startIndex.value]) fakeList[i] = allList[i + startIndex.value]
        }
​
        /*
            hideCount虚拟列表隐藏的个数是2,则前面更新索引时,其实虚拟列表渲染的数据只是整体向后移动了一位
            这里就是修正同一个数据的位移偏差,看起来不会抖
        */
        e.target.scrollTop = itemHeight.value
    }
​
    /* 
        上拉时更新列表数据
    */
    function onReachTop() {
        startIndex.value -= hideCount.value
        endIndex.value -= hideCount.value
​
        for (let i = 0; i < fakeList.length; i++) {
            fakeList[i] = allList[i + startIndex.value]
        }
​
        e.target.scrollTop = itemHeight.value
    }
​
}

效果展示

相关推荐
Thomas_YXQ9 小时前
Unity3D Overdraw性能优化详解
开发语言·人工智能·性能优化·unity3d
正在走向自律12 小时前
GpuGeek 网络加速:破解 AI 开发中的 “最后一公里” 瓶颈
网络·人工智能·python·机器学习·性能优化·gpugeek
繁星无法超越12 小时前
详解Windows(九)——系统性能优化
windows·stm32·性能优化
清酒伴风(面试准备中......)1 天前
小白学编程之——数据库如何性能优化
数据库·oracle·性能优化
shadon1781 天前
【鸿蒙开发】性能优化
性能优化·鸿蒙
zhengddzz1 天前
从卡顿到丝滑:JavaScript性能优化实战秘籍
开发语言·javascript·性能优化
EQ-雪梨蛋花汤1 天前
【Part 2安卓原生360°VR播放器开发实战】第四节|安卓VR播放器性能优化与设备适配
android·性能优化·vr
csdnzzt2 天前
CUDA编程——性能优化基本技巧
性能优化·矩阵·cuda
北海有初拥2 天前
【从零实现JsonRpc框架#3】线程模型与性能优化
性能优化
开源架构师2 天前
JVM 与云原生的完美融合:引领技术潮流
jvm·微服务·云原生·性能优化·serverless·内存管理·容器化