前端性能优化之虚拟列表

性能优化之虚拟列表

场景

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

描述

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

例如,设定列表容器容纳十个子组件,页面初始第一个子组件渲染的数据索引为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
    }
​
}

效果展示

相关推荐
TrisighT21 小时前
Electron 跑在鸿蒙 PC 上,单窗口和多窗口内存差 800MB?我抓了 5 组数据
性能优化·electron·harmonyos
jump_jump5 天前
流式 HTML:从 htmx 片段装配到浏览器原生增量渲染
javascript·性能优化·前端工程化
小小工匠6 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
大鱼>6 天前
地平线BPU部署实战:YOLOv8在J5/X3上的算法适配与性能优化
算法·yolo·性能优化
醉颜凉6 天前
Elasticsearch高性能优化:Bulk API大规模数据导入性能调优全攻略
elasticsearch·性能优化·jenkins
隔窗听雨眠6 天前
C语言函数递归从入门到精通(下):性能优化与工程实践
c语言·算法·性能优化
昇腾CANN6 天前
【cann-samples系列】GroupedMatmul MX量化矩阵乘的深度性能优化实践
线性代数·性能优化·矩阵·昇腾·cann
霸道流氓气质6 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
步步为营DotNet6 天前
Blazor 与 Microsoft.Extensions.AI 在客户端性能优化中的协同应用
人工智能·microsoft·性能优化
不能只会打代码6 天前
边缘视频分析平台的架构设计与性能优化——从750ms到190ms的调优之路
java·spring boot·redis·性能优化·边缘计算·物联网竞赛