说在前头
关于什么是虚拟列表,这里不赘述。elementui的table组件本身并不支持虚拟列表功能,在对付上100条数据数十列的情况下渲染就很慢,对列表的各种操作都慢,比如点个编辑打开弹窗等等,肉眼可见的延迟。数据越多这样的卡顿越是明显。最近在做的项目就有用到elementui,80%的页面都是各种表格,有的表格要求最低行数100,最高1000,列最少15列,最多达20多列,渲染、操作各项体验非常难受。 所以,虚拟列表势在必行。
明确思路
对于超长的表格,在请求到数据之后,先取15条出来交给table组件渲染。剩下的,在滚动列表个时候监听一下表格的滚动条,在逐步添加数据,暴露在视野中的就渲染,其他直接false掉。这里有一个问题需要解决,就是初始下的15条并不能给表格带来符合100条的滚动高度,这里提供两种办法:
- 使用一个标签,设置高度为总长度的高 单行的高 * 当前页总条数
- 不使用标签,直接使用伪元素,给伪元素设置高 单行的高 * 当前页总条数
在改变每页条数的情况下,需要修改这个高度,考虑到js不容易修改伪元素的高度,所以采用真实标签的做法。
开搞
在进入__mounted__生命周期时,对__table组件初始化__。
let i = document.createElement('i')
i.id = 'vheight'
i.style.width = '0'
i.style.float = 'right'
document.querySelector('.el-table__body-wrapper').append(i)
document.querySelector('.el-table__body').style.float = 'left'
this.tableData2 = {} //tableData2不需要在页面显示,故不放入data中
同时,对表格的滚动条添加监听事件
document.querySelector('.el-table__body-wrapper').addEventListener('scroll', () => {
//xxx
})
在拿到数据之后对数据做截断处理
$.ajax({
url: url,
type: 'get',
data: {},
dataType: 'json',
success: (data) => {
if (data.code === 1) {
this.tableData2 = data.data // 将这个总的数据存起来
// 拿到数据之后给i加上高度撑出滚动条,42是每行的高度
document.querySelector('#vheight').style.height = this.tableData2.leadsList.length * 42 + 'px'
// 截断的前15条数据放入tableData,交给table组件渲染
this.tableData = {
leadsList: this.tableData2.leadsList.filter((x,i) => i >= 0 && i < (15)),
count: this.tableData2.count
}
}
}
})
回过头,添加滚动事件的逻辑
document.querySelector('.el-table__body-wrapper').addEventListener('scroll', () => {
let s = document.querySelector('.el-table__body-wrapper').scrollTop, h = 42, c = ''
c = Math.floor(s / h)
// 16为横向滚动条的高度
if (s >= (this.tableData2.leadsList.length * 42) - document.querySelector('.el-table__body-wrapper').offsetHeight - 16) {
s = (this.tableData2.leadsList.length * 42) - document.querySelector('.el-table__body-wrapper').offsetHeight
}
if (s <= 0) { s = 0 }
document.querySelector('.el-table__body-wrapper .el-table__body').style.transform = `translateY(${s}px)`
this.tableData = {
leadsList: this.tableData2.leadsList.filter((x,i) => i >= c && i < (c + 15)),
count: this.tableData2.count
}
this.$nextTick(() => {
if (this.selectIdx.length > 0) {
let s = this.tableData.leadsList.map(x => {
if (this.selectIdx.includes(x.leads_id)) {
this.$refs.mulTable.toggleRowSelection(x)
}
return x
})
}
if (this.selectIdx.length > 0 && this.selectIdx.length < this.pageSize) {
setTimeout(() => {
let pro = document.querySelector('.el-table-column--selection')
pro.querySelector('.el-checkbox__input').classList.add('is-indeterminate')
pro.querySelector('.el-checkbox__input').classList.remove('is-checked')
}, 100)
}
})
})
到这里,基本的功能初步实现了。接下来就是处理表格的多选。
表多选
在table组件上绑定__select-all、select__两个事件。
@select-all="handleSelectionAll" @select="handleSelectionChange"
methods 中添加对应的方法和逻辑
methods: {
// selectIdx存放被勾选的行
handleSelectionChange (val, v) {
let i = this.selectIdx.indexOf(v.leads_id)
if (i !== -1) {
this.selectIdx.splice(i , 1)
} else {
this.selectIdx.push(v.leads_id)
}
},
// 表头全选
handleSelectionAll (val) {
// 有值表示全选,反之亦然
if (val.length > 0) {
this.selectIdx = this.tableData2.leadsList.map(x => x.leads_id)
} else {
this.selectIdx = []
}
},
}
在滚动监听事件中处理滚动勾选
this.$nextTick(() => {
if (this.selectIdx.length > 0) {
let s = this.tableData.leadsList.map(x => {
if (this.selectIdx.includes(x.leads_id)) {
this.$refs.mulTable.toggleRowSelection(x)
}
return x
})
}
// 半选下强制修改状态
if (this.selectIdx.length > 0 && this.selectIdx.length < this.pageSize) {
setTimeout(() => {
let pro = document.querySelector('.el-table-column--selection')
pro.querySelector('.el-checkbox__input').classList.add('is-indeterminate')
pro.querySelector('.el-checkbox__input').classList.remove('is-checked')
}, 100)
}
})
使用css实现table的fixed 功能
table组件的__fixed属性__会固定列,但是一个fixed属性它就复制一份表格,影响渲染速度。所以,可以使用css的__sticky属性__来代替。
.fixed-table {
td:first-child, th:first-child {
position: sticky;
left: 0;
z-index: 1;
background: #fff;
}
td:last-child {
position: sticky;
right: 0;
z-index: 1;
background: #fff;
border-left: 1px solid #ebeef5;
}
th:nth-last-child(2) {
position: sticky;
right: 17px;
z-index: 1;
border-left: 1px solid #d1d2d6;
}
.gutter {
width: 17px;
position: sticky;
right: 0;
z-index: 1;
}
}
一些小问题的解决
- 改变每页显示条数和翻页后移除选中项
handleSizeChange: function (val) {
this.currentPage = 1
this.pageSize = val
this.selectIdx = []
},
handleCurrentChange: function (val) {
this.selectIdx = []
this.currentPage = val
},
- 重新发起请求后滚动回顶部并清除选择项
document.querySelector('.el-table__body-wrapper').scrollTop = 0
this.selectIdx = []