vue+element-ui el-table组件二次封装实现虚拟滚动,解决数据量大渲染DOM过多而卡顿问题

一、此功能已集成到TTable组件中

二、最终效果

在这里插入图片描述

三、需求

某些页面不做分页时,当数据过多,会导致页面卡顿,甚至卡死

四、虚拟滚动

一、固定一个可视区域的大小并且其大小是不变的,那么要做到性能最大化就需要尽量少地渲染 DOM 元素,而这个最小值也就是可视范围内需要展示的内容,而可视区域之外的元素均可以不做渲染。
二、如何计算可视区域内需要渲染的元素,我们通过如下几步来实现虚拟滚动:

1、每一行的高度需要相同,方便计算。
2、需要知道渲染的数据量(数组长度),可基于总量和每个元素的高度计算出容器整体的所需高度,这样就可以伪造一个真实的滚动条。
3、获取可视区域的高度。
4、在滚动事件触发后,滚动条的距顶距离即这个数据量中的偏移量,再根据可视区域本身的高度,算出本次偏移量,这样就得到了需要渲染的具体数据

五、具体实现(源码)

<template><div class="t-table" id="t_table"><el-tableref="el-table":data="tableData":class="{cursor: isCopy,row_sort: isRowSort,highlightCurrentRow: highlightCurrentRow,radioStyle: table.firstColumn && table.firstColumn.type === 'radio',treeProps: isShowTreeStyle,is_sort_icon:onlyIconSort}":max-height="useVirtual?maxHeight||540:maxHeight"v-bind="$attrs"v-on="$listeners":highlight-current-row="highlightCurrentRow":border="table.border || isTableBorder":span-method="spanMethod || objectSpanMethod":cell-class-name="cellClassNameFuc"@sort-change="soltHandle"@row-click="rowClick"@cell-dblclick="cellDblclick"><!-- 主体内容 --><template v-for="(item, index) in renderColumns"><el-table-columnv-if="item.isShowCol === false ? item.isShowCol : true":key="index + 'i'":type="item.type":label="item.label":prop="item.prop":min-width="item['min-width'] || item.minWidth || item.width":sortable="item.sort || sortable":align="item.align || 'center'":fixed="item.fixed":show-overflow-tooltip="useVirtual?true:item.noShowTip?false:true"v-bind="{ ...item.bind, ...$attrs }"v-on="$listeners"><template slot-scope="scope">...</template></el-table-column></template></el-table></div>
</template><script>
export default {name: 'TTable',props: {// table所需数据table: {type: Object,default: () => {return {}}// required: true},// 表头数据columns: {type: Array,default: () => {return []}// required: true},...// Table最大高度maxHeight: {type: [String, Number]},// 是否开启虚拟列表useVirtual: {type: Boolean,default: false}},data() {return {tableData: this.table?.data,/*** 虚拟列表*/saveDATA: [], // 所有数据tableRef: null, // 设置了滚动的那个盒子tableWarp: null, // 被设置的transform元素fixLeft: null, // 固定左侧--设置的transform元素fixRight: null, // 固定右侧--设置的transform元素tableFixedLeft: null, // 左侧固定列所在的盒子tableFixedRight: null, // 右侧固定列所在的盒子scrollTop: 0,scrollNum: 0, // scrollTop / (itemHeight * pageList)start: 0,end: 30, // 3倍的pageListstarts: 0, // 备份ends: 30, // 备份pageList: 10, // 一屏显示itemHeight: 48 // 每一行高度}},watch: {'table.data': {handler(val) {if (this.useVirtual) {this.saveDATA = valthis.tableData = this.saveDATA.slice(this.start, this.end)} else {this.tableData = val}},deep: true // 深度监听},scrollNum(newV) {// 因为初始化时已经添加了3屏的数据,所以只有当滚动到第3屏时才计算位移量if (newV > 1) {this.start = (newV - 1) * this.pageListthis.end = (newV + 2) * this.pageListrequestAnimationFrame(() => {// 计算偏移量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)})} else {requestAnimationFrame(() => {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)`}})}}},created() {// 是否开启虚拟列表if (this.useVirtual) {this.init()}},mounted() {// 是否开启虚拟列表if (this.useVirtual) {this.initMounted()}},methods: {initMounted() {this.$nextTick(() => {// 设置了滚动的盒子this.tableRef = this.$refs['el-table'].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'// 新创建的盒子divWarpChildlet 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)})},// 初始化数据init() {this.saveDATA = this.table?.datathis.tableData = this.saveDATA.slice(this.start, this.end)},// 滚动事件onScroll() {this.scrollTop = this.tableRef.scrollTopthis.scrollNum = Math.floor(this.scrollTop / (this.itemHeight * this.pageList))}}
}
</script>

六、源码地址

GitHub源码地址

Gitee源码地址

基于ElementUi或Antd再次封装基础组件文档

vue3+ts基于Element-plus再次封装基础组件文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/95967.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

梯度下降算法入门

提到梯度下降我们知道梯度下降算法是很多机器学习算法、深度学习算法的基础。 首先我们需要明确一些概念什么是梯度&#xff1a; 梯度的本意是一个向量&#xff08;矢量&#xff09;&#xff0c;表示某一函数在该点处的方向导数沿着该方向取得最大值&#xff0c;即函数在该点处…

软件测试/测试开发丨Python 内置库 正则表达式

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/27058 python 内置库 正则表达式 目录 正则表达式使用re模块实现正则表达式操作 正则表达式 正则表达式就是记录文本规则的代码可以查找操作符合某些复…

HBuilderX修改manifest.json设置,解决跨域问题(CORS、Cross-Origin)

搭建一个前台uniapp&#xff0c;后台springboot的开发环境时&#xff0c;遇到了跨域问题。 console提示错误信息&#xff1a; Access to XMLHttpRequest at http://10.0.180.203/api/cms/getAdList?apId1 from origin http://localhost:8080 has been blocked by CORS policy…

ROS-5.自定义topic消息格式

自定义topic消息格式 1. 定义消息1.1. 定义msg文件1.2. 在package.xml中添加功能包依赖1.3. 在CMakeList.txt添加编译选项1.4. 编译 2.定义发布者和订阅者2.1 定义发布者2.2. 定义订阅者2.3. 修改CMakeList.txt2.4 编译 3. 使用消息3.1 启动ros主程序3.2. 启动发布者3.3 启动订…

如何制作并运行 jar 程序

以下是用 Intellij 制作 jar 程序&#xff0c;并运行的方法。 【1】新建工程&#xff0c;保持默认选项&#xff0c;Next 【2】保持默认选项&#xff0c;Next 【3】给工程命名&#xff0c;设置保存位置&#xff0c;Finish 【4】新建工程结束&#xff0c;进入开发界面 【5】展开…

ArcGIS将两个相同范围但不同比例或位置的矢量数据移动到相同位置

有两个市图层&#xff0c;一个是正确经纬度的市行政范围图层&#xff0c;另一个是其他软件导出获取的不正确经纬度信息或缺失信息。 如果单纯的依靠移动图层&#xff0c;使不正确的移动到正确位置需要很久。尝试定义投影等也不能解决。 使用ArcMap 的空间校正工具条&#xff…

nnUNet v2数据准备及格式转换 (二)

如果你曾经使用过nnUNet V1&#xff0c;那你一定明白数据集的命名是有严格要求的&#xff0c;必须按照特定的格式来进行命名才能正常使用。 这一节的学习需要有数据&#xff0c;如果你有自己的数据&#xff0c;可以拿自己的数据来实验&#xff0c;如果没有&#xff0c;可以用十…

【负载均衡】常见的负载均衡策略有哪些?

文章目录 前言负载均衡分类常见负载均衡策略小结 前言 负载均衡策略是实现负载均衡器的关键&#xff0c;而负载均衡器又是分布式系统中不可或缺的重要组件。使用它有助于提高系统的整体性能、可用性、可靠性和安全性&#xff0c;同时支持系统的扩展和故障容忍性。对于处理大量…

JavaScript运行机制与实践应用

一、JavsScript运行机制 1、JavaScript 是一种解释型语言&#xff0c;它的执行机制主要包括以下几个步骤&#xff1a; 2、事件循环 3、JavaScript运行模型 4、JavaScript任务 5、JavaScript宏任务和微任务 6、案例分析 console.log(script start) setTimeout(function () {co…

c语言tips-c语言的虚函数实现

0. 前言 学过面对对象的同学都知道虚函数是面向对象编程中的一个重要概念&#xff0c;它允许在基类和派生类之间实现多态性&#xff08;polymorphism&#xff09;。我们可以在基类去定义一个成员函数&#xff0c;然后再派生类再去覆盖写它&#xff0c;这样在不同派生类使用相同…

Flutter 项目结构文件

1、Flutter项目的文件结构 先helloworld项目&#xff0c;看看它都包含哪些组成部分。首先&#xff0c;来看一下项目的文件结构&#xff0c;如下图所示。 2、介绍上图的内容。 -litb/main.dart文件&#xff1a;整个应用的入口文件&#xff0c;其中的main函数是整个Flutter应…

TCP Header都有啥?

分析&回答 源端口号&#xff08;Source Port&#xff09; &#xff1a;16位&#xff0c;标识主机上发起传送的应用程序&#xff1b; 目的端口&#xff08;Destonation Port&#xff09; &#xff1a;16位&#xff0c;标识主机上传送要到达的应用程序。 源端&#xff0c;目…