一、背景
当前系统平台的不少页面比如(分布式机器学习相关的页面)需要渲染大量的数据,但是由于数据存储hdfs中,不能用分页的形式去展示只能接口一次性返回所有的数据,在这个情况下如果使用普通的渲染方式,把接口返回的数据一次性全部渲染到当前这个页面上,会导致页面出现渲染过慢,卡顿,滚动不流畅严重影响用户体验,更严重着可能导致页面直接崩溃,用户体验极差,这在产品当中肯定是不允许的,所以这个时候解决大数据渲染的问题就是必要的而且是迫切的。
二、分析
一个页面所能渲染地dom数是有一定限制的,而且一个页面渲染的dom越多,cpu内存开销就越大,一帧所消耗的时间就越长,整个页面看起来就越卡顿,所以要想页面交互流畅体验性好,页面当中就不能过多的去渲染很多个dom ,但是当前的业务需求就是需要一次性展示大量的数据,所以只能从尽量少的渲染dom,然后尽量多的去展示数据的思路去考虑解决这个性能瓶颈的问题,这里少渲染dom多展示数据就意味着只能限制渲染的数量,在这个渲染数量dom的基础上通过滚动或者是下拉来展示不同的数据,渲染的dom数量不变通过来回切不同的数据,达到大数据展示同时又不需要去过多的渲染dom,这在技术手段上叫虚拟渲染的方式,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。
三、设计方案
1.虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除。
- 计算当前
可视区域起始数据索引(startIndex) - 计算当前
可视区域结束数据索引(endIndex) - 计算当前
可视区域的数据,并渲染到页面中 - 计算
startIndex对应的数据在整个列表中的偏移位置startOffset并设置到列表上
由于只是对可视区域内的列表项进行渲染,所以为了保持列表容器的高度并可正常的触发滚动,将Html结构设计成如下结构:
<div class="infinite-list-container">
<div class="infinite-list-phantom"></div>
<div class="infinite-list">
<!-- item-1 --> <!-- item-2 --> <!-- ...... --> <!-- item-n → </div>
</div>
infinite-list-container为可视区域的容器infinite-list-phantom为容器内的占位,宽度为总列表的总宽度,用于形成滚动条infinite-list为列表项的渲染区域
则可推算出:
- 列表总宽度
listWidth= listData.length * itemSize - 可显示的列表项数
visibleCount= Math.ceil(screenHeight / itemSize) - 数据的起始索引
startIndex= Math.floor(scrollTop / itemSize) - 数据的结束索引
endIndex= startIndex + visibleCount - 列表显示数据为
visibleData= listData.slice(startIndex,endIndex)
当滚动后,由于渲染区域相对于可视区域已经发生了偏移,此时我需要获取一个偏移量startOffset,通过样式控制将渲染区域偏移至可视区域中。
- 偏移量
startOffset= scrollLeft - (scrollLeft % itemSize);
当前是横向滚动大数据渲染的实现原理和思路,纵向滚动大数据虚拟滚动的实现原理和思路是类似的,本文就不在详细往下展开
四、效果
a.横向滚动
b.纵向滚动效果
五、集成步骤
1.下载 npm i vxe-table@3.5.8 -S npm install vite-plugin-style-import -D
**2.**引入到入口文件
import { Column, Table, Grid } from "vxe-table";import "vxe-table/lib/style.css";Vue.use(Column).use(Table).use(Grid);
3.babelrc得配置
4.代码参考
a.横向滚动
<div class="listDetail-container"> <virtual-table v-else :data="data" key="dataPreview" :columns="columns" :heightMax="heightMax" :remain="remain" :loading="loading" :itemWidth="itemWidth" ></virtual-table> </div>
b.纵向滚动
<template> <div> <VxeTableCus v-if="modelShow" border show-overflow show-header-overflow ref="xTable" :data="data" row-id="name" :height="maxHeight" :scroll-y="{ mode: 'default ' }" :sort-config="{ trigger: 'cell' }" @radio-change="radioChangeEvent" @checkbox-change="selectChangeEvent" :checkbox-config="checkboxConfig" :radio-config="checkboxConfig" > <vxe-column :type="changeType" width="60"></vxe-column> <vxe-column field="name" title="数据名称" width="200"></vxe-column> <vxe-column field="type" title="类型" width="200"></vxe-column> </VxeTableCus> </div></template><script> import mlStore from "../mixins/mlStore"; import { hiveOrderSchemas } from "@/api/distMachine"; export default { data() { return { tempSelectItem: [], maxHeight: 480, changeType: this.itemType == "col" ? "radio" : "checkbox", checkboxConfig: { checkMethod: this.checCheckboxkMethod2.bind(this), highlight: true, }, }; }, methods: { getSelectedCell({ row, column }) {}, //请求hiveSchemas async requestHiveSchemas(nodes, pNode) { const idNode = await this.getSuperNode(nodes, pNode); try { const res = await hiveOrderSchemas({ id: idNode && idNode.attrs.parameters.dataId.value, clusterId: this.clusterId, }); let hiveDatas = []; for (let v of res) { hiveDatas.push({ name: v.split(",")?.[0], type: v.split(",")?.[1], }); } hiveDatas.map((ele) => { if ( this.attrItem.typeValid && this.attrItem.typeValid.includes(ele.type) ) { ele.disabled = true; } else if (!this.attrItem.typeValid) { ele.disabled = true; } else { ele.disabled = true; } }); this.maxHeight = hiveDatas.length < 10 ? (hiveDatas.length + 1) * 48 : 480; this.$refs.xTable.options("reloadData", hiveDatas); } catch (e) { this.$refs.xTable.options("reloadData", []); } finally { } }, }, components: { VxeTableCus: () => import("./assets/it-uiue/components/virtualTable/vxe-table"), }, };</script>
5.文档api连接
https://xuliangzhan\_admin.gitee.io/vxe-table/#/table/start/use