为什么要用虚拟滚动
当表格不以分页形式展示,如果数据过多,那么dom树会将数据节点全部加载出来,如下图所示:
假如从后端获取的数据有成千上万条导致了页面卡顿,又不想改成分页展示,那么你需要用“虚拟滚动”来处理。
虚拟滚动能够实现展示所有数据,但是不在DOM树中渲染全部节点,进而优化了性能!如下图所示
什么是虚拟滚动
先知道什么是真实滚动,发生滚动前tbody顶部正好紧贴于表格内部。
当你滚动后,效果如下:
第一个节点跑到了表格的上面,也就是说tbody的位置发生了位移,这就是真实的滚动。
所谓虚拟滚动,顾名思义不是tbody在表格中位移,而是用另一个元素在表格内代替tbody滚动。
虚拟滚动实现原理
如下图所示,限制表格只渲染表格可视区域个数的数据节点,然后在el-table的el-table__body-wrapper的盒子内塞一个div,你可以理解为是伪造的tbody,div的高度 = 一行单元格的高度 * 表格数组长度。
发生滚动时,伪造的tbody进行滚动,而真正的tbody纹丝不动,只改变tbody中已选染的数据节点(个数保持不变)。
发生滚动时,通过计算得出展示数据的索引值,然后在存储所有展示数据的数组里,截取部分数据出来展示,如此虚拟滚动基本功能已实现。
Demo
<template>
<div>
<div class="gl-cell-card-box">
<el-table
ref="tableRef"
style="width:418px"
border
max-height="480"
:data="sliceTable"
:row-key="row => row.id"
@select="handleSelect"
@select-all="handleSelectAll"
>
<el-table-column type="selection" width="40"> </el-table-column>
<el-table-column prop="name" label="姓名" width="120"></el-table-column>
<el-table-column prop="age" label="年龄" width="120"></el-table-column>
<el-table-column prop="address" label="住址"></el-table-column>
</el-table>
</div>
</div>
</template>
<script>
export default {
data() {
return {
// 表格所有数据
tableData:[],
// 开始索引
startIndex: 0,
// 选中的数据
selectedRows: [],
// 空元素,用于撑开table的高度
vEle: undefined,
// 是否全选
isSelectedAll: false,
};
},
// 计算属性
computed: {
// 这个是截取表格中的部分数据,放到了 table 组件中来显示
sliceTable() {
return this.tableData.slice(
this.startIndex, ((this.tableData.length - this.startIndex) > 9) ? (this.startIndex + 9) : this.tableData.length);
},
},
created() {
// 创建一个空元素,这个空元素用来撑开 table 的高度,模拟所有数据的高度
this.vEle = document.createElement("div");
},
mounted() {
this.getData();
this.loadData();
// 绑定滚动事件
this.$refs.tableRef.$el
.querySelector(".el-table__body-wrapper")
.addEventListener("scroll", this.tableScroll, {
passive: true
});
},
methods: {
getData(){
let start_i = this.tableData.length;
console.log(start_i);
for (let i = start_i; i < start_i + 200; i++) {
this.tableData.push({
id: i,
name: "zhangsan" + i,
age: 12,
address: "china"
});
}
console.log(this.tableData);
},
// 加载数据
loadData() {
this.$nextTick(() => {
// 设置成绝对定位,这个元素需要我们去控制滚动
this.$refs.tableRef.$el.querySelector(".el-table__body").style.position = "absolute";
// 计算表格所有数据所占内容的高度
this.vEle.style.height = this.tableData.length * 48 + "px";
// 把这个节点加到表格中去,用它来撑开表格的高度
this.$refs.tableRef.$el.querySelector(".el-table__body-wrapper").appendChild(this.vEle);
// 重新设置曾经被选中的数据
this.selectedRows.forEach(row => {
this.$refs.tableRef.toggleRowSelection(row, true);
});
});
},
/**
* @description: 手动勾选时的事件
* @param {*} selection - 选中的所有数据
* @param {*} row - 当前选中的数据
* @return {*}
*/
handleSelect(selection, row) {
this.selectedRows = selection;
},
/**
* @description: 全选事件
* @param {*} selection
* @return {*}
*/
handleSelectAll(selection) {
this.isSelectedAll = !this.isSelectedAll;
if (this.isSelectedAll) {
this.selectedRows = this.tableData;
} else {
this.selectedRows = [];
this.$refs.tableRef.clearSelection();
}
},
/**
* @description: table 滚动事件
* @param {*}
* @return {*}
*/
tableScroll() {
let bodyWrapperEle = this.$refs.tableRef.$el.querySelector(".el-table__body-wrapper");
// 滚动的高度
let scrollTop = bodyWrapperEle.scrollTop;
// 下一次开始的索引
this.startIndex = Math.floor(scrollTop / 48);
console.log(scrollTop, this.startIndex,'scrollTop, startIndex');
// 滚动操作
bodyWrapperEle.querySelector(".el-table__body").style.transform = `translateY(${this.startIndex * 48}px)`;
// 滚动操作后,上面的一些 tr 没有了,所以需要重新设置曾经被选中的数据
this.$nextTick(() => {
this.selectedRows.forEach(row => {
this.$refs.tableRef.toggleRowSelection(row, true);
});
})
// 滚动到底,加载新数据
if (bodyWrapperEle.scrollHeight <= scrollTop + bodyWrapperEle.clientHeight) {
this.$message.warning("没有更多了");
return;
}
}
},
}
</script>
<style>
</style>
参考文献
https://blog.csdn.net/Hyanl/article/details/129304466?spm=1001.2014.3001.5506