前端性能优化之虚拟列表

性能优化之虚拟列表

场景

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

描述

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

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

效果展示

相关推荐
之歆2 小时前
Day16_JavaScript 轮播图与事件工程实战(下篇)
服务器·开发语言·前端·javascript·网络·性能优化
UWA7 小时前
5秒快速开玩:小游戏性能优化实战
性能优化·游戏开发·minigame·particlesystem
MU在掘金916958 小时前
让LLM按维度自动切换分析策略:SmartInspector 的 Prompt Skill 系统
性能优化
2501_916007479 小时前
iOS应用性能优化全面指南:从内存管理到工具使用
android·ios·性能优化·小程序·uni-app·iphone·webview
代码小书生10 小时前
Windows系统优化设置,电脑系统工具箱!支持远程桌面控制、性能优化调节、功能选项增强设置、驱动安装更新、系统更新管理、安全配置与系统维护!
windows·性能优化·系统优化·电脑系统·电脑技巧·windows10·电脑优化
光泽雨10 小时前
ADO.NET 进阶知识与实战坑位深度解析
性能优化·架构·.net
wbs_scy11 小时前
MySQL 索引特性与性能优化全解
性能优化
霞姐聊IT12 小时前
缓存技术:从CPU Cache到AI KV Cache (一)
缓存·性能优化
朝阳58113 小时前
树莓派跑了个 M3U8 下载服务,内存从 600MB 降到 2MB
性能优化·rust
梵得儿SHI13 小时前
SpringCloud 进阶拓展:性能优化指南(缓存三大问题 + 分库分表入门)
spring cloud·缓存·微服务·性能优化·高并发·分库分表·数据库优化