element表格el-table實(shí)現(xiàn)虛擬滾動(dòng)解決卡頓問(wèn)題
當(dāng)頁(yè)面數(shù)據(jù)過(guò)多,前端渲染大量的DOM時(shí),會(huì)造成頁(yè)面卡死問(wèn)題,使用分頁(yè)或則懶加載這些方案也無(wú)法解決,這些處理方法在頁(yè)面加載到足夠多的數(shù)據(jù)的時(shí)候,隨著頁(yè)面追加渲染的DOM越來(lái)越多,也會(huì)導(dǎo)致頁(yè)面卡頓,甚至卡死。這時(shí)候我們可以把兩個(gè)方案中和一下,既然在有限的視窗中我們只能看到一部分的數(shù)據(jù),那么我們就通過(guò)計(jì)算可視范圍內(nèi)的單元格,這樣就保證了每一次拖動(dòng),我們渲染的 DOM 元素始終是可控的,不會(huì)像數(shù)據(jù)分頁(yè)方案怕一次性渲染過(guò)多,也不會(huì)發(fā)生無(wú)限滾動(dòng)方案中的老數(shù)據(jù)堆積現(xiàn)象。所以就有了虛擬滾動(dòng)這一方案。
虛擬滾動(dòng)
接下來(lái)我們用一張圖來(lái)表示虛擬滾動(dòng)的表現(xiàn)形式。
根據(jù)圖中我們可以看到,無(wú)論我們?nèi)绾螡L動(dòng),我們可視區(qū)域的大小其實(shí)是不變的,那么要做到性能最大化就需要盡量少地渲染 DOM 元素,而這個(gè)最小值也就是可視范圍內(nèi)需要展示的內(nèi)容,也就是圖中的綠色區(qū)塊,在可視區(qū)域之外的元素均可以不做渲染。
那么問(wèn)題就來(lái)了,如何計(jì)算可視區(qū)域內(nèi)需要渲染的元素,我們通過(guò)如下幾步來(lái)實(shí)現(xiàn)虛擬滾動(dòng):
- 每一行的高度需要相同,方便計(jì)算
- 需要得知渲染的數(shù)據(jù)量(數(shù)組長(zhǎng)度),可基于總量和每個(gè)元素的高度計(jì)算出容器整體的所需高度,這樣就可以偽造一個(gè)真實(shí)的滾動(dòng)條
- 獲取可視區(qū)域的高度
- 在滾動(dòng)事件觸發(fā)后,滾動(dòng)條的距頂距離也可以理解為這個(gè)數(shù)據(jù)量中的偏移量,再根據(jù)可視區(qū)域本身的高度,算出本次偏移的截止量,這樣就得到了需要渲染的具體數(shù)據(jù)
- 如果類似于渲染一個(gè)寬表,單行可橫向拆分為多列,那么在X軸上同理實(shí)現(xiàn)一次,就可以橫向的虛擬滾動(dòng)
效果如圖:
<template> <el-table :data="tableData" ref="tableRef" style="width: 900px" max-height="380" border stripe class="myTable" > <el-table-column prop="date" label="必要元素:" min-width="150" align="center" fixed="left" > </el-table-column> <el-table-column label="每一行高度必須相同"> <el-table-column prop="name" label="class不能為【myTable】" min-width="180" align="center" > </el-table-column> <el-table-column label="ref不能為【tableRef】"> <el-table-column prop="province" label="省份" min-width="150" align="center" > </el-table-column> <el-table-column prop="city" label="市區(qū)" min-width="150" align="center" > </el-table-column> <el-table-column prop="address" label="地址" min-width="150" align="center" > </el-table-column> </el-table-column> </el-table-column> <el-table-column label="操作" fixed="right" min-width="160" align="center"> <template> <el-button size="mini">編輯</el-button> <el-button size="mini" type="danger">刪除</el-button> </template> </el-table-column> </el-table> </template> <script> export default { data() { return { tableData: [], // 需要渲染的數(shù)據(jù) saveDATA: [], // 所有數(shù)據(jù) tableRef: null, // 設(shè)置了滾動(dòng)的那個(gè)盒子 tableWarp: null, fixLeft: null, fixRight: null, tableFixedLeft: null, tableFixedRight: null, scrollTop: 0, num: 0, start: 0, end: 42, // 3倍的pageList starts: 0, // 備份[保持與上一樣] ends: 42, // 備份[保持與上一樣] pageList: 14, // 一屏顯示 itemHeight: 41, // 每一行高度 timeOut: 400 // 延遲 } }, watch: { num: function(newV) { // 因?yàn)槌跏蓟瘯r(shí)已經(jīng)添加了3屏的數(shù)據(jù),所以只有當(dāng)滾動(dòng)到第3屏?xí)r才計(jì)算位移量 if (newV > 1) { this.start = (newV - 1) * this.pageList this.end = (newV + 2) * this.pageList // 使用延時(shí)器會(huì)造成頁(yè)面短暫白屏的問(wèn)題,使用requestAnimationFrame則可以解決 // setTimeout(() => { // // 計(jì)算偏移量 // 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) // }, this.timeOut) requestAnimationFrame(() => { // 計(jì)算偏移量 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)` } }) // setTimeout(() => { // 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)` // } // }, this.timeOut) } } }, created() { this.init() }, mounted() { this.$nextTick(() => { // 設(shè)置了滾動(dòng)的盒子 this.tableRef = this.$refs.tableRef.bodyWrapper // 左側(cè)固定列所在的盒子 this.tableFixedLeft = document.querySelector( '.el-table .el-table__fixed .el-table__fixed-body-wrapper' ) // 右側(cè)固定列所在的盒子 this.tableFixedRight = document.querySelector( '.el-table .el-table__fixed-right .el-table__fixed-body-wrapper' ) /** * fixed-left | 主體 | fixed-right */ // 主體改造 // 創(chuàng)建內(nèi)容盒子divWarpPar并且高度設(shè)置為所有數(shù)據(jù)所需要的總高度 let divWarpPar = document.createElement('div') // 如果這里還沒(méi)獲取到saveDATA數(shù)據(jù)就渲染會(huì)導(dǎo)致內(nèi)容盒子高度為0,可以通過(guò)監(jiān)聽saveDATA的長(zhǎng)度后再設(shè)置一次高度 divWarpPar.style.height = this.saveDATA.length * this.itemHeight + 'px' // 新創(chuàng)建的盒子divWarpChild let divWarpChild = document.createElement('div') divWarpChild.className = 'fix-warp' // 把tableRef的第一個(gè)子元素移動(dòng)到新創(chuàng)建的盒子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) // 被設(shè)置的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) }) }, methods: { init() { this.saveDATA = [] for (let i = 0; i < 10000; i++) { this.saveDATA.push({ date: i, name: '王小虎' + i, address: '1518', province: 'github:', city: 'divcssjs', zip: 'divcssjs' + i }) } this.tableData = this.saveDATA.slice(this.start, this.end) }, onScroll() { this.scrollTop = this.tableRef.scrollTop this.num = Math.floor(this.scrollTop / (this.itemHeight * this.pageList)) } } } </script> <style lang="less" scoped> .myTable { /deep/ td { padding: 6px 0 !important; } } /*滾動(dòng)條樣式*/ /deep/ .el-table__body-wrapper::-webkit-scrollbar { /*滾動(dòng)條整體樣式*/ width: 6px; /*高寬分別對(duì)應(yīng)橫豎滾動(dòng)條的尺寸*/ height: 8px; } /deep/ .el-table__body-wrapper::-webkit-scrollbar-thumb { /*滾動(dòng)條里面小方塊*/ border-radius: 2px; background: #666; } /deep/ .el-table__body-wrapper::-webkit-scrollbar-track { /*滾動(dòng)條里面軌道*/ background: #ccc; } </style>
使用虛擬列表做投屏功能
投屏頁(yè)面 buletinFullscreen.vue
<!-- * @Last Modified by: Damon * @Last Modified time: 2023-2-14 * @content: 訂單看板投屏 --> <template> <!-- 設(shè)置虛擬列表滾動(dòng)條的盒子 --> <div id="buletinFullscreen"> <div class="list-top"> <p class="list-tit">訂單看板</p> <ul class="list-lists"> <li>已接單(數(shù)量): <span style="color: #008300">1</span></li> <li>生產(chǎn)中(數(shù)量): <span style="color: #D3831A">2</span></li> <li>出貨中(數(shù)量): <span style="color: #128BFF">3</span></li> <li>出貨完成(數(shù)量): <span style="color: #585858">4</span></li> </ul> <button class="cancel-btn" size="mini" @click="exitListFullScreen" >{{ $t('qxtp') }}</button> </div> <!-- 設(shè)置虛擬列表這個(gè)盒子的總高度,即所有數(shù)據(jù)顯示所需要的高度,比如100*163,其中100為所有數(shù)據(jù)條數(shù),163為每條列表展示的高度 --> <div class="list-div-content" id="buletinScrollBox" ref="contentBox"> <!-- 真正展示內(nèi)容的盒子,也是設(shè)置transform的盒子 --> <div id="buletinScrollContent" v-if="tableDataFullscreen && tableDataFullscreen.length>0"> <!-- 每一項(xiàng) --> <div class="list-title-div" v-for="(item) in tableDataFullscreen" :key="item.id"> {{ item.code }} </div> </div> </div> </div> </template> <script> export default { components: {}, props: {}, data () { return { tableDataFullscreen: [], // 需要顯示的數(shù)據(jù) scrollDomBox: null, // 設(shè)置了滾動(dòng)的那個(gè)盒子 noScroll: true, // 是否沒(méi)有滾動(dòng)條 isBottom: false, setIntervalTimer: null, setTimeoutTimer: null, setTimeoutTimer1: null, allTableDataFullscreen: [], // 所有數(shù)據(jù) tableWarp: null, scrollTop: 0, num: 0, start: 0, end: 90, // 3倍的pageList starts: 0, // 備份[保持與上一樣] ends: 90, // 備份[保持與上一樣] pageList: 30, // 需要顯示的數(shù)據(jù)的條數(shù) clientList: 10, // 可視區(qū)域能顯示的條數(shù) itemHeight: 110, // 每一行高度 } }, watch: { isBottom: function() { // 觸底和觸頂后重新請(qǐng)求一次數(shù)據(jù) setTimeout(() => { this.clearIntervalFn() this.getListData(true) },5000) }, num: function(newV) { // 因?yàn)槌跏蓟瘯r(shí)已經(jīng)添加了3屏的數(shù)據(jù),所以只有當(dāng)滾動(dòng)到第3屏?xí)r才計(jì)算位移量 console.log(newV,'newV'); if (newV > 1) { this.start = (newV - 1) * this.pageList this.end = (newV + 2) * this.pageList requestAnimationFrame(() => { // 計(jì)算偏移量 this.tableWarp.style.transform = `translateY(${this.start * this.itemHeight}px)` this.tableDataFullscreen = this.allTableDataFullscreen.slice(this.start, this.end) }) } else { requestAnimationFrame(() => { this.tableDataFullscreen = this.allTableDataFullscreen.slice(this.starts, this.ends) this.tableWarp.style.transform = `translateY(0px)` }) } } }, created() {}, mounted() { this.getListData() }, destroyed() { // 清除定時(shí)器和延時(shí)器 this.clearIntervalFn() this.clearTimeoutFn(this.setTimeoutTimer) this.clearTimeoutFn(this.setTimeoutTimer1) // 取消監(jiān)聽全屏事件 this.removeScreenFn() }, methods: { // 計(jì)算當(dāng)前屏數(shù) onScroll() { this.scrollTop = this.scrollDomBox.scrollTop this.num = Math.floor(this.scrollTop / (this.itemHeight * this.pageList)) }, clearIntervalFn() { clearInterval(this.setIntervalTimer) }, clearTimeoutFn(timer) { clearTimeout(timer) }, getListData(isFalg) { if (!isFalg) { this.listenerScreenFn() } let arr = [] // 模擬異步請(qǐng)求 setTimeout(() => { for (var i = 0; i < 10000; i++) { let obj = { code: '列表' + i } arr.push(obj) } this.allTableDataFullscreen = arr; // 首屏的時(shí)候要多加上一屏的數(shù)據(jù)才能無(wú)縫銜接 const count = this.pageList + this.clientList; this.tableDataFullscreen = this.allTableDataFullscreen.slice(0,count) this.$nextTick(() => { // 設(shè)置了滾動(dòng)的盒子 this.scrollDomBox = document.getElementById("buletinFullscreen"); // 高度設(shè)置為所有數(shù)據(jù)所需要的總高度 let divWarpPar = document.getElementById('buletinScrollBox') // 如果這里還沒(méi)獲取到數(shù)據(jù)就渲染會(huì)導(dǎo)致內(nèi)容盒子高度為0,可以通過(guò)監(jiān)聽長(zhǎng)度后再設(shè)置一次高度 // 頭部高度為80 if (divWarpPar) { divWarpPar.style.height = this.allTableDataFullscreen.length * this.itemHeight + 80 + 'px' } console.log(this.allTableDataFullscreen.length * this.itemHeight,'kkk'); // 被設(shè)置的transform元素 this.tableWarp = document.getElementById('buletinScrollContent') this.scrollDomBox && this.scrollDomBox.addEventListener('scroll', this.onScroll) let scrollBox = document.getElementById("buletinFullscreen"); // 電腦屏幕的刷新率為60左右 this.setIntervalTimer = setInterval(() => { this.autoScroll(scrollBox) this.onScroll() },60) }) },2000) }, // 自動(dòng)滾動(dòng) autoScroll(scrollDom) { if ((scrollDom.scrollTop + scrollDom.clientHeight < scrollDom.scrollHeight && !this.isBottom) || !scrollDom.scrollTop) { scrollDom.scrollTop += 1 this.isBottom = false } else { // 觸底則向上滾動(dòng) scrollDom.scrollTop -= 1 this.isBottom = true } if (!scrollDom.scrollTop && this.noScroll) { this.noScroll = false // 沒(méi)有滾動(dòng)條時(shí)觸發(fā) this.setTimeoutTimer1 = setTimeout(() => { this.clearIntervalFn() this.getListData() },30000) } }, /** * @description 全屏 * requestFullscreen方法必須由用戶主動(dòng)交互觸發(fā),否則會(huì)報(bào)錯(cuò) */ listFullScreen() { const element = document.documentElement if (element.requestFullscreen) { element.requestFullscreen() } else if (element.mozRequestFullScreen) { element.mozRequestFullScreen() } else if (element.msRequestFullscreen) { element.msRequestFullscreen() } else if (element.webkitRequestFullscreen) { element.webkitRequestFullscreen() } }, /** * @description 退出全屏 */ exitListFullScreen() { if (document.fullscreenElement) { document.exitFullscreen() } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen() } else if (document.msExitFullscreen) { document.msExitFullscreen() } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen() } }, changeFullscreen() { console.log('監(jiān)聽全屏事件'); const fullscreenEle = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement console.log(fullscreenEle,'fullscreenEle'); let flag = false if (fullscreenEle) { //瀏覽器進(jìn)入全屏 flag = true } else { flag = false } this.$emit('changeShowFlag',flag) }, /** * @description 監(jiān)聽全屏事件 */ listenerScreenFn() { this.listFullScreen() if (document.exitFullscreen) { document.addEventListener('fullscreenchange', this.changeFullscreen) } else if (document.mozCancelFullScreen) { document.addEventListener('mozfullscreenchange', this.changeFullscreen) } else if (document.msExitFullscreen) { document.addEventListener('MSFullscreenChange', this.changeFullscreen) } else if (document.webkitCancelFullScreen) { document.addEventListener('webkitfullscreenchange', this.changeFullscreen) } }, /** * @description 移除全屏事件 */ removeScreenFn() { if (document.exitFullscreen) { document.removeEventListener('fullscreenchange', this.changeFullscreen) } else if (document.mozCancelFullScreen) { document.removeEventListener('mozfullscreenchange', this.changeFullscreen) } else if (document.msExitFullscreen) { document.removeEventListener('MSFullscreenChange', this.changeFullscreen) } else if (document.webkitCancelFullScreen) { document.removeEventListener('webkitfullscreenchange', this.changeFullscreen) } }, } } </script> <style lang="less" scoped> #buletinFullscreen { position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow-y: auto; z-index: 1002; .list-top{ position: fixed; z-index: 1003; width:100%; transition: top .3s ease; border-bottom:1px solid #ebeef2 ; margin-left: 0px; padding: 0 15px; height: 80px; display: flex; align-items: center; color: #333333; .cancel-btn { position: absolute; top: 25px; right: 15px; } } .list-tit{ margin-right: 30px; margin-left: 10px; font-weight: 500; font-size: 24px; } .list-lists{ display: flex; margin-top: 0px; li{ margin-right: 30px; } } .list-div-content{ padding: 95px 15px 15px; background-color: #ebeef2; min-height: 100%; overflow: hidden; .list-title-div{ height: 100px; width: 100%; display: flex; flex-direction: column; justify-content: center; padding: 10px 15px; background: #ffffff; margin-bottom: 10px; position: relative; overflow: hidden; } } } </style>
引入投屏頁(yè)
<template> <div> <button size="mini" @click="handlerClick" >投屏</button> <!-- 投屏頁(yè)面 必須使用v-if --> <buletin-fullscreen v-if="isShowFullScreen" @changeShowFlag="handlerClick"></buletin-fullscreen> </div> </template> <script> import buletinFullscreen from '~/components/bulletin/buletinFullscreen.vue'; export default { components: { buletinFullscreen }, data() { return { isShowFullScreen:false,// 是否全屏 }; }, mounted() { window.addEventListener("keydown", this.keyDownFuns)// 監(jiān)聽按鍵事件 }, methods: { // 監(jiān)聽F11鍵盤事件 keyDownFuns(event) { if (event.keyCode === 122) { event.preventDefault(); this.handlerClick(true) } }, handlerClick(flag) { this.isShowFullScreen= flag } } }; </script> <style lang="less" scoped> </style>
element表格組件實(shí)現(xiàn)虛擬滾動(dòng),解決卡頓問(wèn)題
「前端進(jìn)階」高性能渲染十萬(wàn)條數(shù)據(jù)(時(shí)間分片)
到此這篇關(guān)于element表格el-table實(shí)現(xiàn)虛擬滾動(dòng)解決卡頓問(wèn)題的文章就介紹到這了,更多相關(guān)element 表格el-table虛擬滾動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue+EleMentUI實(shí)現(xiàn)el-table-colum表格select下拉框可編輯功能實(shí)例
- vue element-ui實(shí)現(xiàn)el-table表格多選以及回顯方式
- el-element中el-table表格嵌套el-select實(shí)現(xiàn)動(dòng)態(tài)選擇對(duì)應(yīng)值功能
- 關(guān)于Element-ui中el-table出現(xiàn)的表格錯(cuò)位問(wèn)題解決
- element el-table表格的二次封裝實(shí)現(xiàn)(附表格高度自適應(yīng))
- VUE2.0+ElementUI2.0表格el-table實(shí)現(xiàn)表頭擴(kuò)展el-tooltip
- VUE2.0+ElementUI2.0表格el-table循環(huán)動(dòng)態(tài)列渲染的寫法詳解
- element el-table如何實(shí)現(xiàn)表格動(dòng)態(tài)增加/刪除/編輯表格行(帶校驗(yàn)規(guī)則)
相關(guān)文章
基于Vue2.0和Typescript實(shí)現(xiàn)多主題切換的示例
本文主要介紹了基于Vue2.0和Typescript實(shí)現(xiàn)多主題切換的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04vue項(xiàng)目中跳轉(zhuǎn)到外部鏈接的實(shí)例講解
今天小編就為大家分享一篇vue項(xiàng)目中跳轉(zhuǎn)到外部鏈接的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09從Vue到Postman全面驗(yàn)證API接口跨域問(wèn)題解決
我們都知道跨域是同源策略導(dǎo)致的,域名不同、協(xié)議不同、端口號(hào)不同任意一種情況都會(huì)導(dǎo)致跨域,這篇文章主要介紹了從Vue到Postman全面驗(yàn)證API接口跨域問(wèn)題,需要的朋友可以參考下2024-08-08