【Vue】VUE,ElementUI列表大数据卡顿问题?(已解决)

基于elementUI的table,在不修改源码的情况下支持大数据了渲染的场景

2019-7-18又优化了一版(后面遇到BUG,就直接更改这个了),相当于重写,用了指令的一些特性;完善了下注释

var EventUtil = {

/*检测绑定事件*/

addHandler: function (element, type, handler) {

if (element.addEventListener) {

element.addEventListener(type, handler, false);

} else if (element.attachEvent) {

element.attachEvent('on' + type, handler);

} else {

element["on" + type] = handler /*直接赋给事件*/

}

},

/*通过removeHandler*/

removeHandler: function (element, type, handler) {

/*Chrome*/

if (element.removeEventListener)

element.removeEventListener(type, handler, false);

else if (element.deattachEvent) {

/*IE*/

element.deattachEvent('on' + type, handler);

} else {

element["on" + type] = null;

/*直接赋给事件*/

}

}

};

// 设置默认溢出显示数量

var spillDataNum = 5;

// 设置隐藏函数

let timeout = false;

let triggerDataIndexChange = function (el, binding, vnode, oldVnode, currentTableHeight) {

if (timeout) {

clearTimeout(timeout);

}

const elTableBodyWrapper = el.querySelector('.el-table__body-wrapper');

const virtualDiv = elTableBodyWrapper.querySelector('div.virtual-div');

const elTableBody = elTableBodyWrapper.querySelector('table.el-table__body');

const elTableRow = elTableBodyWrapper.querySelector('table tr.el-table__row');

const context = vnode.context;

const tableData = context.tableData;

const elTableBodyWrapperHeight = elTableBodyWrapper.clientHeight;

const elTableRowHeight = elTableRow.clientHeight;

const showRowNum = Math.round(elTableBodyWrapperHeight / elTableRowHeight);

let maxScrollTop = virtualDiv.clientHeight - elTableBodyWrapper.clientHeight - spillDataNum * elTableRowHeight;

// 处理滚动条置顶的情况

if (elTableBodyWrapper.scrollTop === 0) {

context.currentStartIndex = 0;

context.currentEndIndex = tableData.length + spillDataNum;

elTableBody.setAttribute('style', 'transform: translateY(0px);width: ' + elTableBody.style.width + ';');

return;

}

// 处理滚动条滚动中和置低的情况

let scrollTopNum = elTableBodyWrapper.scrollTop / elTableRowHeight;

let currentStartIndex = scrollTopNum;

let currentEndIndex = scrollTopNum + showRowNum + spillDataNum;

if (currentEndIndex - tableData.length >= 0) {

context.currentStartIndex = tableData.length - 1 - showRowNum - spillDataNum;

context.currentEndIndex = tableData.length - 1;

elTableBody.setAttribute('style', 'transform: translateY(' + maxScrollTop + 'px);width: ' + elTableBody.style.width + ';');

return;

}

context.currentStartIndex = currentStartIndex;

context.currentEndIndex = currentEndIndex;

elTableBody.setAttribute('style', 'transform: translateY(' + (elTableBodyWrapper.scrollTop) + 'px);width: ' + elTableBody.style.width + ';');

};

// 滚动条置低

let handleScrollEnd = function (el) {

const elTableBodyWrapper = el.querySelector('.el-table__body-wrapper');

elTableBodyWrapper.scrollTop = elTableBodyWrapper.scrollHeight;

};

export default {

name: 'loadmore',

inserted: function (el, binding, vnode, oldVnode) {

const isLoadmore = vnode.data.attrs['is-loadmore'];

if (isLoadmore === 'NoScrollBar') {

return;

}

const elTableBodyWrapper = el.querySelector('.el-table__body-wrapper');

const elTableBodyWrapperChildren = elTableBodyWrapper.children;

// 创建虚拟DIV,用来设置数据高度,触发滚动条高度更新

const virtualDiv = document.createElement('div');

virtualDiv.setAttribute('class', "virtual-div");

elTableBodyWrapper.appendChild(virtualDiv);

virtualDiv.appendChild(elTableBodyWrapperChildren[0]);

// 滚动事件

let handleMoveScroll = function () {

triggerDataIndexChange(el, binding, vnode, oldVnode);

}

EventUtil.addHandler(elTableBodyWrapper, "scroll", handleMoveScroll);

},

update() {},

componentUpdated: function (el, binding, vnode, oldVnode) {

// 获取组件缓存的数据

const dataSize = vnode.data.attrs['data-size'];

const oldDataSize = oldVnode.data.attrs['data-size'];

const isLoadmore = vnode.data.attrs['is-loadmore'];

const isAddRowData = vnode.data.attrs['is-addRowData'];

// 获取必要的DOM节点

const elTableBodyWrapper = el.querySelector('.el-table__body-wrapper');

const elTableRow = elTableBodyWrapper.querySelector('table tr.el-table__row');

let elTableRowHeight = 0;

if (elTableRow) {

elTableRowHeight = elTableRow.clientHeight;

}

if (elTableRowHeight === 0) {

elTableRowHeight = 25;

}

// 如果是设置不需要滚动条的模式,直接显示所有数据,并设置表格的高度为数据的高度

if (isLoadmore === 'NoScrollBar') {

const context = vnode.context;

let tableHeight = (dataSize + 1) * elTableRowHeight + 40;

context.currentTableHeight = tableHeight;

return;

}

if (dataSize) {

// 根据行高计算当前应该显示那些数据

// 根据数据多少和行高设置虚拟滚动DIV的高度

const virtualDiv = elTableBodyWrapper.querySelector('div.virtual-div');

const virtualDivHeight = dataSize * elTableRowHeight;

virtualDiv.setAttribute('style', `height: ${virtualDivHeight}px;`);

// 首次进来,即currentStartIndex为零,如果可显示区域大于默认显示20条数据。要重新赋值默认显示的数量为显示区域的数量加上默认溢出的数量

const bodyHeight = vnode.componentInstance.layout.height;

const showRowNum = Math.round(bodyHeight / elTableRowHeight);

const currentStartIndex = vnode.context.currentStartIndex;

const currentEndIndex = vnode.context.currentEndIndex;

if (currentStartIndex === 0 && bodyHeight && showRowNum > currentEndIndex) {

vnode.context.currentEndIndex = showRowNum + spillDataNum;

}

}

// 新增数据处理,直接将滚动条置底

if (isAddRowData) {

handleScrollEnd(el);

}

}

};

问题描述

很早之前的问题,后来根据网友的回答,又优化了一些版本。今天整理下,感谢寻梦无痕提供的帮助

场景

前端UI框架使用的是ElementUI,项目要求数据不分页一个表格至少要1000条数据,这时点击其他DOM操作,会出现卡顿的现象。如点击复选框。

现状

已解决3000条数据点击其他DOM操作卡顿的问题。

求助

助如何解决初次放入数据时卡顿的情况?(内附解决加载完成后卡顿的思路)**

结果分享

优化了大概三个版本,保留第一次的分享的,后面直接贴出最终版
解决3000条数据点击其他DOM操作卡顿的思路
通过指令添加表格滚动后的事件,设置非显示区域的其他DOM节点隐藏。

指令代码

// 设置隐藏函数

var timeout = false;

let setRowDisableNone = function (selectRows, topNum, rowHeight, showRowNum) {

if (timeout) {

clearTimeout(timeout);

}

timeout = setTimeout(() => {

selectRows.forEach((selectRow, index) => {

const selectRowsTds = selectRow.querySelectorAll('td');

if (topNum <= index && index <= (topNum + showRowNum)) {

selectRowsTds.forEach((selectRowsTd) => {

selectRowsTd.style.display = '';

});

} else {

selectRow.style.height = `${rowHeight}px`;

selectRowsTds.forEach((selectRowsTd) => {

selectRowsTd.style.display = 'none';

});

}

});

}, 100);

};

export default {

name: 'loadmore',

componentUpdated: function (el) {

setTimeout(() => {

const selectWrap = el.querySelector('.el-table__body-wrapper');

const selectRow = selectWrap.querySelector('table tr');

const rowHeight = selectRow.clientHeight;

const selectRows = selectWrap.querySelectorAll('table tr');

setRowDisableNone(selectRows, 0, rowHeight, showRowNum);

// 监听滚动事件,操作DOM隐藏和显示

selectWrap.addEventListener('scroll', function () {

let topPx = this.scrollTop;

let topNumber = Math.round(topPx / rowHeight);

let rowTopLastIndex = topNumber;

setRowDisableNone(selectRows, rowTopLastIndex, rowHeight, showRowNum);

})

});

}

};

有同学说componentUpdated执行不了,这个我是通过指令做的,后面我贴出完成的代码和例子

解决性能问题(最终版)

参考评论区寻梦无痕分享的例子,整合以下,感谢。

思路

减少对DOM节点的渲染,通过滚动函数节流实现滚动后事件来动态渲染数据

Element Table代码

<template>

<div>

<el-table border :data="filteredData" height="300" :data-size="tableData.length" v-loadmore="handelLoadmore">

<el-table-column type="selection" width="55">

</el-table-column>

<el-table-column label="日期" width="180">

<template slot-scope="scope">

<div>

<i class="el-icon-time"></i>

<span>{{ scope.row.date }}</span>

</div>

</template>

</el-table-column>

<el-table-column label="日期" width="180">

<template slot-scope="scope">

<div>

<i class="el-icon-time"></i>

<span>{{ scope.row.date }}</span>

</div>

</template>

</el-table-column>

<el-table-column label="日期" width="180">

<template slot-scope="scope">

<div>

<i class="el-icon-time"></i>

<span>{{ scope.row.date }}</span>

</div>

</template>

</el-table-column>

<el-table-column label="日期" width="180">

<template slot-scope="scope">

<div>

<i class="el-icon-time"></i>

<span>{{ scope.row.date }}</span>

</div>

</template>

</el-table-column>

<el-table-column label="姓名" width="180">

<template slot-scope="scope">

<div>

<el-popover trigger="hover" placement="top">

<p>姓名: {{ scope.row.name }}</p>

<p>住址: {{ scope.row.address }}</p>

<div slot="reference" class="name-wrapper">

<el-tag size="medium">{{ scope.row.name }}</el-tag>

</div>

</el-popover>

</div>

</template>

</el-table-column>

<el-table-column label="操作">

<template slot-scope="scope">

<div>

<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>

<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>

</div>

</template>

</el-table-column>

</el-table>

</div>

</template>

<script>

export default {

name: 'test',

components: {},

data () {

return {

tableData: [],

currentStartIndex: 0,

currentEndIndex: 20

};

},

created () {

this.getTableData();

},

computed: {

filteredData () {

return this.tableData.filter((item, index) => {

if (index < this.currentStartIndex) {

return false;

} else if (index > this.currentEndIndex) {

return false;

} else {

return true;

}

});

}

},

methods: {

handelLoadmore (currentStartIndex, currentEndIndex) {

this.currentStartIndex = currentStartIndex;

this.currentEndIndex = currentEndIndex;

},

getTableData () {

let cont = 0;

let tableData = [];

while (cont < 30000) {

cont = cont + 1;

let object = {

date: cont,

name: '王小虎' + cont,

address: '上海市普陀区金沙江路 cont 弄'

}

tableData.push(object);

}

setTimeout(() => {

this.tableData = tableData;

}, 2000);

}

},

watch: {}

}

</script>

<style scoped>

.el-table__body-wrapper .el-table__row td {

display: none;

}

.el-table__body-wrapper .el-table__row {

height: 38px;

}

</style>

指令代码

// 设置默认溢出显示数量

var spillDataNum = 20;

// 设置隐藏函数

var timeout = false;

let setRowDisableNone = function (topNum, showRowNum, binding) {

if (timeout) {

clearTimeout(timeout);

}

timeout = setTimeout(() => {

binding.value.call(null, topNum, topNum + showRowNum + spillDataNum);

});

};

export default {

name: 'loadmore',

componentUpdated: function (el, binding, vnode, oldVnode) {

setTimeout(() => {

const dataSize = vnode.data.attrs['data-size'];

const oldDataSize = oldVnode.data.attrs['data-size'];

if(dataSize === oldDataSize){

return;

}

const selectWrap = el.querySelector('.el-table__body-wrapper');

const selectTbody = selectWrap.querySelector('table tbody');

const selectRow = selectWrap.querySelector('table tr');

if (!selectRow) {

return;

}

const rowHeight = selectRow.clientHeight;

let showRowNum = Math.round(selectWrap.clientHeight / rowHeight);

const createElementTR = document.createElement('tr');

let createElementTRHeight = (dataSize - showRowNum - spillDataNum) * rowHeight;

createElementTR.setAttribute('style', `height: ${createElementTRHeight}px;`);

selectTbody.append(createElementTR);

// 监听滚动后事件

selectWrap.addEventListener('scroll', function () {

let topPx = this.scrollTop - spillDataNum * rowHeight;

let topNum = Math.round(topPx / rowHeight);

let minTopNum = dataSize - spillDataNum - showRowNum;

if (topNum > minTopNum) {

topNum = minTopNum;

}

if (topNum < 0) {

topNum = 0;

topPx = 0;

}

selectTbody.setAttribute('style', `transform: translateY(${topPx}px)`);

createElementTR.setAttribute('style', `height: ${createElementTRHeight-topPx > 0 ? createElementTRHeight-topPx : 0}px;`);

setRowDisableNone(topNum, showRowNum, binding);

})

});

}

};

注释比较简单,要注释的地方太多了,就看代码吧。还有很多值得优化的地方。

代码中使用的是全局注册指令,我就不贴代码了

回答

可以尝试用这个整合下Element UI https://clusterize.js.org/
看他官网的例子,生成10W+级别的数据滑动也丝毫不卡顿,用的类似卷帘的技术,超出的部分自动移除了。

https://github.com/livelyPeng... 流畅渲染万级数据并不会影响到 element-ui el-table组件的原有功能,并且新增了一些功能

不管怎么处理,只要页面上的数据元素很大,卡是必然的。
你分段加载最后界面上依然是很多元素。
chrome中如果感觉有点卡的话,IE估计就没法用了....
之前做的elementUI下拉选择2000多条卡成狗,最后换成了远程搜索。

发现个问题,当‘操作’列固定在右边时,这个table的数据滚动到底部列表就错位了
【Vue】VUE,ElementUI列表大数据卡顿问题?(已解决)

分段加载, 先加载 2屏的数据, 再加载剩余的数据

你这个问题解决了吗,我现在实验你上边说的这个方法,componentUpdated里的事件根本不能执行啊,求助求助

兄弟,有没有源码呢?

最近正好在优化这个table,谢谢作者

有的地方不理解啊,老哥,该讲下思路呗。

好像在表格使用固定列fixed的时候,滚动的时表格会错位。

可视区域全选时表头的状态为全选,这个问题遇到了嘛

可以试试vxe-table
https://xuliangzhan_admin.git...
功能还是比较强的
嵌套表单也没问题

表格每行的高度不固定怎么办

博主,建议使用requestAnimationFrame替代setTimeOut;没有滚动效果是咋回事呢?

有办法在超出溢出数量后有滚动特效吗,目前是没有滚动特效的,楼主可否给我点主意呢,我刚接触vue不久

感谢楼主分享,但还是有些bug,感觉不太和我要求,所以目前我使用了这个插件
https://www.npmjs.com/package...

以上是 【Vue】VUE,ElementUI列表大数据卡顿问题?(已解决) 的全部内容, 来源链接: utcz.com/a/71875.html

回到顶部