element表格el-table實現(xiàn)虛擬滾動解決卡頓問題
當頁面數(shù)據(jù)過多,前端渲染大量的DOM時,會造成頁面卡死問題,使用分頁或則懶加載這些方案也無法解決,這些處理方法在頁面加載到足夠多的數(shù)據(jù)的時候,隨著頁面追加渲染的DOM越來越多,也會導(dǎo)致頁面卡頓,甚至卡死。這時候我們可以把兩個方案中和一下,既然在有限的視窗中我們只能看到一部分的數(shù)據(jù),那么我們就通過計算可視范圍內(nèi)的單元格,這樣就保證了每一次拖動,我們渲染的 DOM 元素始終是可控的,不會像數(shù)據(jù)分頁方案怕一次性渲染過多,也不會發(fā)生無限滾動方案中的老數(shù)據(jù)堆積現(xiàn)象。所以就有了虛擬滾動這一方案。
虛擬滾動
接下來我們用一張圖來表示虛擬滾動的表現(xiàn)形式。

根據(jù)圖中我們可以看到,無論我們?nèi)绾螡L動,我們可視區(qū)域的大小其實是不變的,那么要做到性能最大化就需要盡量少地渲染 DOM 元素,而這個最小值也就是可視范圍內(nèi)需要展示的內(nèi)容,也就是圖中的綠色區(qū)塊,在可視區(qū)域之外的元素均可以不做渲染。
那么問題就來了,如何計算可視區(qū)域內(nèi)需要渲染的元素,我們通過如下幾步來實現(xiàn)虛擬滾動:
- 每一行的高度需要相同,方便計算
- 需要得知渲染的數(shù)據(jù)量(數(shù)組長度),可基于總量和每個元素的高度計算出容器整體的所需高度,這樣就可以偽造一個真實的滾動條
- 獲取可視區(qū)域的高度
- 在滾動事件觸發(fā)后,滾動條的距頂距離也可以理解為這個數(shù)據(jù)量中的偏移量,再根據(jù)可視區(qū)域本身的高度,算出本次偏移的截止量,這樣就得到了需要渲染的具體數(shù)據(jù)
- 如果類似于渲染一個寬表,單行可橫向拆分為多列,那么在X軸上同理實現(xiàn)一次,就可以橫向的虛擬滾動
效果如圖:


<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è)置了滾動的那個盒子
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) {
// 因為初始化時已經(jīng)添加了3屏的數(shù)據(jù),所以只有當滾動到第3屏時才計算位移量
if (newV > 1) {
this.start = (newV - 1) * this.pageList
this.end = (newV + 2) * this.pageList
// 使用延時器會造成頁面短暫白屏的問題,使用requestAnimationFrame則可以解決
// setTimeout(() => {
// // 計算偏移量
// 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(() => {
// 計算偏移量
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è)置了滾動的盒子
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')
// 如果這里還沒獲取到saveDATA數(shù)據(jù)就渲染會導(dǎo)致內(nèi)容盒子高度為0,可以通過監(jiān)聽saveDATA的長度后再設(shè)置一次高度
divWarpPar.style.height = this.saveDATA.length * this.itemHeight + 'px'
// 新創(chuàng)建的盒子divWarpChild
let divWarpChild = document.createElement('div')
divWarpChild.className = 'fix-warp'
// 把tableRef的第一個子元素移動到新創(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;
}
}
/*滾動條樣式*/
/deep/ .el-table__body-wrapper::-webkit-scrollbar {
/*滾動條整體樣式*/
width: 6px;
/*高寬分別對應(yīng)橫豎滾動條的尺寸*/
height: 8px;
}
/deep/ .el-table__body-wrapper::-webkit-scrollbar-thumb {
/*滾動條里面小方塊*/
border-radius: 2px;
background: #666;
}
/deep/ .el-table__body-wrapper::-webkit-scrollbar-track {
/*滾動條里面軌道*/
background: #ccc;
}
</style>使用虛擬列表做投屏功能
投屏頁面 buletinFullscreen.vue
<!--
* @Last Modified by: Damon
* @Last Modified time: 2023-2-14
* @content: 訂單看板投屏
-->
<template>
<!-- 設(shè)置虛擬列表滾動條的盒子 -->
<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è)置虛擬列表這個盒子的總高度,即所有數(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">
<!-- 每一項 -->
<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è)置了滾動的那個盒子
noScroll: true, // 是否沒有滾動條
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() {
// 觸底和觸頂后重新請求一次數(shù)據(jù)
setTimeout(() => {
this.clearIntervalFn()
this.getListData(true)
},5000)
},
num: function(newV) {
// 因為初始化時已經(jīng)添加了3屏的數(shù)據(jù),所以只有當滾動到第3屏時才計算位移量
console.log(newV,'newV');
if (newV > 1) {
this.start = (newV - 1) * this.pageList
this.end = (newV + 2) * this.pageList
requestAnimationFrame(() => {
// 計算偏移量
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() {
// 清除定時器和延時器
this.clearIntervalFn()
this.clearTimeoutFn(this.setTimeoutTimer)
this.clearTimeoutFn(this.setTimeoutTimer1)
// 取消監(jiān)聽全屏事件
this.removeScreenFn()
},
methods: {
// 計算當前屏數(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 = []
// 模擬異步請求
setTimeout(() => {
for (var i = 0; i < 10000; i++) {
let obj = {
code: '列表' + i
}
arr.push(obj)
}
this.allTableDataFullscreen = arr;
// 首屏的時候要多加上一屏的數(shù)據(jù)才能無縫銜接
const count = this.pageList + this.clientList;
this.tableDataFullscreen = this.allTableDataFullscreen.slice(0,count)
this.$nextTick(() => {
// 設(shè)置了滾動的盒子
this.scrollDomBox = document.getElementById("buletinFullscreen");
// 高度設(shè)置為所有數(shù)據(jù)所需要的總高度
let divWarpPar = document.getElementById('buletinScrollBox')
// 如果這里還沒獲取到數(shù)據(jù)就渲染會導(dǎo)致內(nèi)容盒子高度為0,可以通過監(jiān)聽長度后再設(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)
},
// 自動滾動
autoScroll(scrollDom) {
if ((scrollDom.scrollTop + scrollDom.clientHeight < scrollDom.scrollHeight && !this.isBottom) || !scrollDom.scrollTop) {
scrollDom.scrollTop += 1
this.isBottom = false
} else {
// 觸底則向上滾動
scrollDom.scrollTop -= 1
this.isBottom = true
}
if (!scrollDom.scrollTop && this.noScroll) {
this.noScroll = false
// 沒有滾動條時觸發(fā)
this.setTimeoutTimer1 = setTimeout(() => {
this.clearIntervalFn()
this.getListData()
},30000)
}
},
/**
* @description 全屏
* requestFullscreen方法必須由用戶主動交互觸發(fā),否則會報錯
*/
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) {
//瀏覽器進入全屏
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>引入投屏頁
<template>
<div>
<button
size="mini"
@click="handlerClick"
>投屏</button>
<!-- 投屏頁面 必須使用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表格組件實現(xiàn)虛擬滾動,解決卡頓問題
「前端進階」高性能渲染十萬條數(shù)據(jù)(時間分片)
到此這篇關(guān)于element表格el-table實現(xiàn)虛擬滾動解決卡頓問題的文章就介紹到這了,更多相關(guān)element 表格el-table虛擬滾動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue+EleMentUI實現(xiàn)el-table-colum表格select下拉框可編輯功能實例
- vue element-ui實現(xiàn)el-table表格多選以及回顯方式
- el-element中el-table表格嵌套el-select實現(xiàn)動態(tài)選擇對應(yīng)值功能
- 關(guān)于Element-ui中el-table出現(xiàn)的表格錯位問題解決
- element el-table表格的二次封裝實現(xiàn)(附表格高度自適應(yīng))
- VUE2.0+ElementUI2.0表格el-table實現(xiàn)表頭擴展el-tooltip
- VUE2.0+ElementUI2.0表格el-table循環(huán)動態(tài)列渲染的寫法詳解
- element el-table如何實現(xiàn)表格動態(tài)增加/刪除/編輯表格行(帶校驗規(guī)則)
相關(guān)文章
基于Vue2.0和Typescript實現(xiàn)多主題切換的示例
本文主要介紹了基于Vue2.0和Typescript實現(xiàn)多主題切換的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-04-04

