1
web 技术分享| 虚拟列表实现
作者:anyRTC开发者
- 2022 年 9 月 14 日 上海
本文字数:1713 字
阅读完需:约 6 分钟
针对过多数据列表展示造成过多节点渲染使页面卡死或卡顿,特地封装一个简易的虚拟列表,大家可在此基础上进行针对修改组件基于 vue3 + element plus + ts + tailwindcss 开发
设计思路
分为三部分:
父容器占位;
一个子容器展示通过滚动条计算的数据相当于真实渲染节点;
一个子容器作为虚拟列表不渲染列表提供列表真实高度显示滚动条
实现
页面结构
<el-scrollbar class="scrollbar_custom px-4" :height="virtualRecord.height" @scroll="handleScroll" > <!-- 虚拟高度 --> <div class="-z-10 absolute inset-0" :style="{ height: virtualRecord.virtualHeight + 'px' }" ></div> <!-- 真实列表 --> <ul :class="['absolute inset-0', prop.customClass]" :style="{ transform: `translateY(${virtualRecord.offset}px)` }" > <li v-for="(item, index) in virtualRecord.visibleData" :key="index + '_'" :style="{ height: virtualRecord.itemHeight + 'px', }" class="hover:bg-anyrtc-gray_8 text-sm" > <!-- 插槽 --> <slot :item="item"></slot> </li> </ul> </el-scrollbar>
复制代码
样式
修改 element plus 的 scrollbar 样式
.scrollbar_custom { :deep(.el-scrollbar__wrap) { .el-scrollbar__view { @apply relative; }}
复制代码
逻辑
组件所需参数
const prop = defineProps({ // 自定义类名 customClass: String, // 相关配置 option: { type: Object, default: () => { return {}; }, }, listData: { type: Array, default: () => { return []; }, },});
复制代码
组件内部定义
// 组件记录(默认)const virtualRecord = reactive({ height: 400, // 展示几个 visibleCount: 16, // // 刷新频率 timeout: 4, // // 行高 itemHeight: 40, // translateY偏移量 offset: 0, // 虚拟占位高度 virtualHeight: 300,
// 记录滚动高度 recordScrollTop: 0,
dataList: [] as any[], // 可展示的数据 visibleData: [] as any[],});
// 合并配置const mergeFn = () => { virtualRecord.height = prop.option.height || 400; virtualRecord.dataList = JSON.parse(JSON.stringify(prop.listData)); virtualRecord.itemHeight = prop.option.itemHeight || 40; virtualRecord.timeout = prop.option.timeout || 4; // // 虚拟高度 virtualRecord.virtualHeight = prop.listData.length * virtualRecord.itemHeight;
// 展示数量 virtualRecord.visibleCount = Math.ceil( virtualRecord.height / virtualRecord.itemHeight );};
复制代码
滚动计算
let lastTime = 0;const handleScroll = (scroll: { scrollTop: number }) => { let currentTime = +new Date(); if (currentTime - lastTime > virtualRecord.timeout) { virtualRecord.recordScrollTop = scroll.scrollTop; updateVisibleData(scroll.scrollTop); lastTime = currentTime; }};const updateVisibleData = (scrollTop: number) => { let start = Math.floor(scrollTop / virtualRecord.itemHeight) - Math.floor(virtualRecord.visibleCount / 2); start = start < 0 ? 0 : start; const end = start + virtualRecord.visibleCount * 2; virtualRecord.visibleData = virtualRecord.dataList.slice(start, end); virtualRecord.offset = start * virtualRecord.itemHeight;};
复制代码
列表信息变更
watch( () => { return [prop.listData, prop.option]; }, ([newData, newHeight]) => { if (newData) { // 合并数据 mergeFn(); // 更新视图 updateVisibleData(virtualRecord.recordScrollTop); } }, { immediate: true, });
复制代码
使用
<VirtualItem :option={height:'占位高度'} :list-data="列表" > <template #default="{ item }"> {{ item }} </template></VirtualItem>
复制代码
划线
评论
复制
发布于: 刚刚阅读数: 4
版权声明: 本文为 InfoQ 作者【anyRTC开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/6eb64c3453eb8c5fc70383eaf】。文章转载请联系作者。
anyRTC开发者
关注
实时交互,万物互联! 2020.08.10 加入
实时交互,万物互联,全球实时互动云服务商领跑者!










评论