添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

element表格el-table组件实现虚拟滚动,解决数据量大渲染DOM过多而卡顿问题

当页面数据过多,前端渲染大量的DOM时,会造成页面卡死问题,使用分页或则懒加载这些方案也无法解决,这些处理方法在页面加载到足够多的数据的时候,随着页面追加渲染的DOM越来越多,也会导致页面卡顿,甚至卡死。
这时候我们可以把两个方案中和一下,既然在有限的视窗中我们只能看到一部分的数据,那么我们就通过计算可视范围内的单元格,这样就保证了每一次拖动,我们渲染的 DOM 元素始终是可控的,不会像数据分页方案怕一次性渲染过多,也不会发生无限滚动方案中的老数据堆积现象。所以就有了虚拟滚动这一方案。

虚拟滚动
接下来我们用一张图来表示虚拟滚动的表现形式。

根据图中我们可以看到,无论我们如何滚动,我们可视区域的大小其实是不变的,那么要做到性能最大化就需要尽量少地渲染 DOM 元素,而这个最小值也就是可视范围内需要展示的内容,也就是图中的绿色区块,在可视区域之外的元素均可以不做渲染。

那么问题就来了,如何计算可视区域内需要渲染的元素,我们通过如下几步来实现虚拟滚动:

每一行的高度需要相同,方便计算

需要得知渲染的数据量(数组长度),可基于总量和每个元素的高度计算出容器整体的所需高度,这样就可以伪造一个真实的滚动条

获取可视区域的高度

在滚动事件触发后,滚动条的距顶距离也可以理解为这个数据量中的偏移量,再根据可视区域本身的高度,算出本次偏移的截止量,这样就得到了需要渲染的具体数据

如果类似于渲染一个宽表,单行可横向拆分为多列,那么在X轴上同理实现一次,就可以横向的虚拟滚动

效果如图:

</el-table-column> </el-table-column> <el-table-column label="操作" fixed="right" min-width="160" align="center"> <template> <el-button size="mini">编辑</el-button> <el-button size="mini" type="danger">删除</el-button> </template> </el-table-column> </el-table> </template> <script> export default { data() { return { tableData: [], // 需要渲染的数据 saveDATA: [], // 所有数据 tableRef: null , // 设置了滚动的那个盒子 tableWarp: null , fixLeft: null , fixRight: null , tableFixedLeft: null , tableFixedRight: null , scrollTop: 0 , num: 0 , start: 0 , end: 42, // 3倍的pageList starts: 0, // 备份[保持与上一样] ends: 42, // 备份[保持与上一样] pageList: 14, // 一屏显示 itemHeight: 41, // 每一行高度 timeOut: 400 // 延迟 created() { this .init() mounted() { this .$nextTick(() => { // 设置了滚动的盒子 this .tableRef = this .$refs.tableRef.bodyWrapper // 左侧固定列所在的盒子 this .tableFixedLeft = document.querySelector( '.el-table .el-table__fixed .el-table__fixed-body-wrapper' // 右侧固定列所在的盒子 this .tableFixedRight = document.querySelector( '.el-table .el-table__fixed-right .el-table__fixed-body-wrapper' * fixed-left | 主体 | fixed-right // 主体改造 // 创建内容盒子divWarpPar并且高度设置为所有数据所需要的总高度 let divWarpPar = document.createElement('div' ) // 如果这里还没获取到saveDATA数据就渲染会导致内容盒子高度为0,可以通过监听saveDATA的长度后再设置一次高度 divWarpPar.style.height = this .saveDATA.length * this .itemHeight + 'px' // 新创建的盒子divWarpChild let divWarpChild = document.createElement('div' ) divWarpChild.className = 'fix-warp' // 把tableRef的第一个子元素移动到新创建的盒子divWarpChild中 divWarpChild.append( this .tableRef.children[0 ]) // 把divWarpChild添加到divWarpPar中,最把divWarpPar添加到tableRef中 divWarpPar.append(divWarpChild) this .tableRef.append(divWarpPar) // left改造 let divLeftPar = document.createElement('div' ) divLeftPar.style.height = this .saveDATA.length * this .itemHeight + 'px' let divLeftChild = document.createElement('div' ) divLeftChild.className = 'fix-left' this .tableFixedLeft && divLeftChild.append( this .tableFixedLeft.children[0 ]) divLeftPar.append(divLeftChild) this .tableFixedLeft && this .tableFixedLeft.append(divLeftPar) // right改造 let divRightPar = document.createElement('div' ) divRightPar.style.height = this .saveDATA.length * this .itemHeight + 'px' let divRightChild = document.createElement('div' ) divRightChild.className = 'fix-right' this .tableFixedRight && divRightChild.append( this .tableFixedRight.children[0 ]) divRightPar.append(divRightChild) this .tableFixedRight && this .tableFixedRight.append(divRightPar) // 被设置的transform元素 this .tableWarp = document.querySelector( '.el-table .el-table__body-wrapper .fix-warp' this .fixLeft = document.querySelector( '.el-table .el-table__fixed .el-table__fixed-body-wrapper .fix-left' this .fixRight = document.querySelector( '.el-table .el-table__fixed-right .el-table__fixed-body-wrapper .fix-right' this .tableRef.addEventListener('scroll', this .onScroll) methods: { init() { this .saveDATA = [] for (let i = 0; i < 10000; i++ ) { this .saveDATA.push({ date: i, name: '王小虎' + i, address: '1518' , province: 'github:' , city: 'divcssjs' , zip: 'divcssjs' + i this .tableData = this .saveDATA.slice( this .start, this .end) onScroll() { this .scrollTop = this .tableRef.scrollTop this .num = Math.floor( this .scrollTop / ( this .itemHeight * this .pageList)) watch: { num: function (newV) { // 因为初始化时已经添加了3屏的数据,所以只有当滚动到第3屏时才计算位移量 if (newV > 1 ) { this .start = (newV - 1) * this .pageList this .end = (newV + 2) * this .pageList setTimeout(() => { // 计算偏移量 this .tableWarp.style.transform = `translateY(${ this .start * this .itemHeight}px)` if ( this .fixLeft) { this .fixLeft.style.transform = `translateY(${ this .start * this .itemHeight}px)` if ( this .fixRight) { this .fixRight.style.transform = `translateY(${ this .start * this .itemHeight}px)` this .tableData = this .saveDATA.slice( this .start, this .end) }, this .timeOut) } else { setTimeout(() => { this .tableData = this .saveDATA.slice( this .starts, this .ends) this .tableWarp.style.transform = `translateY(0px)` if ( this .fixLeft) { this .fixLeft.style.transform = `translateY(0px)` if ( this .fixRight) { this .fixRight.style.transform = `translateY(0px)` }, this .timeOut) </script> <style lang="less" scoped> .myTable { /deep/ td { padding: 6px 0 ! important; /* 滚动条样式 */ /deep/ .el-table__body-wrapper::-webkit- scrollbar { /* 滚动条整体样式 */ width: 6px; /* 高宽分别对应横竖滚动条的尺寸 */ height: 8px; /deep/ .el-table__body-wrapper::-webkit-scrollbar- thumb { /* 滚动条里面小方块 */ border - radius: 2px; background: # 666 ; /deep/ .el-table__body-wrapper::-webkit-scrollbar- track { /* 滚动条里面轨道 */ background: #ccc; </style>