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>