当表格数据量大时,渲染页面时间会很长,影响用户体验
虚拟滚动,当发生滚动时,仅从全部表格数据中截取需要展示的数据进行渲染,从而减少页面渲染时间
注:本案例适用于vue+element ui,其他情况可借鉴思路
数据
data(){
return {
// 全部数据,这里就不赋值啦
data: [],
// 表格切片数据开始索引
startIndex: 0,
// 虚拟滚动元素
vScrollEle: null,
// 实际表格元素
rScrollEle: null,
// 表格包裹元素
tableWrapper: null,
// 表格行高
rowHeight: 40,
}
}
computed(){
// 表格切片数据 只截取需要渲染的部分
slicedData(){
return this.data.slice(this.startIndex, this.startIndex + Math.ceil(this.tableWrapper.clientHeight / rowHeight) > this.data.length ? this.data.length : this.startIndex + Math.ceil(this.tableWrapper.clientHeight / rowHeight))
}
}
HTML+CSS
<el-table ref="tableRef" row-key="id" :data="slicedData">
<!--内容省略啦->
</el-table>
- row-key 用于 diff 算法,同一行不必重复渲染,直接复用
mounted(){
// 创建虚拟滚动的元素
this.vScrollEle = document.createElement('div');
// 设置虚拟滚动元素高度——全部表格数据
this.vScrollEle.style.height = this.data.length;
// 将虚拟滚动元素添加到表格包裹元素中,撑开它
document.querySelector('.el-table__body-wrapper').appendChild(this.vScrollEle);
// 获取实际表格元素.el-table__body
this.rScrollEle = this.vScrollEle.parentNode.children[0];
// 获取表格包裹元素.el-table__body-wrapper
this.bodyWrapper= this.vScrollEle.parentNode;
}
::v-deep .el-table__body {
position: absolute;
}
- 记得将实际表格元素设置为绝对定位
滚动事件监听
mounted(){
// passive设置为true,可优化滚屏性能
this.tableWrapper.addEventListner('scroll', this.scrollHandler, {passive : true});
}
methods(){
scrollHandler(e){
const {scrollTop, clientHeight} = e.target;
// 更新开始索引
this.startIndex = Math.floor(scrollTop / this.rowHeight);
// 滚动实际表格元素
// 判断是否滚动到顶部
if(scrollTop > 0){
// 不是顶部,要回滚一部分,让最下方的行完整呈现
this.rScrollEle.style.top = `${scrollTop - (clientHeight % this.rowHeight ? 44 - clientHeight % this.rowHeight : 0)}px`;
}else {
this.rScrollEle.style.top = '0px';
}
}
}
固定列
如果表格还设置了固定列,可能会出现以下问题:
1. 滚动时多行高亮问题
因为在设置了固定列后,经过高亮由类名 hover-row 实现,而我们又设置了表格的 row-key 属性
思考了很久,想到了个办法——滚动时删除该类名
scrollHandler(){
for(let hoverRow of document.querySeletorAll('tr.hover-row')){
hoverRow.classList.remove('hover-row');
}
}
2. 固定列高亮错位问题
el-table的固定列是通过复制整个表格实现的
所以我们可以考虑对这个拷贝的表格做完全相同的虚拟滚动操作
虚拟滚动+异步加载树形数据
核心思路: 监听节点的展开和关闭,动态调整虚拟滚动元素的高度,并维护第一层级每一行之前的行数属性rowNum
// el-table的expand-change事件
expandChange(row, expanded){
const vHeight = parseInt(this.vscrollEle.style.height);
if(expanded == false){
this.vscrollEle.style.height = `${vHeight - this.rowHeight}px`;
this.updateRowNum(row, expanded);
}else {
this.$nextTick(()=>{
// 异步加载有子节点
if(row.hasChildren){
this.vscrollEle.style.height = `${vHeight + this.rowHeight}px`;
this.updateRowNum(row, expanded);
}
})
}
}
// 维护第一层级每一行之前的行数
updateRowNum(row, expanded){
let index = this.data.findIndex((item) => item.id === row.id) + 1;
for(; index < this.data.length; index++){
// 请求数据之后添加rowNum属性,并赋值为该行索引
this.data[index].rowNum += expanded ? 1 : -1;
}
}