基于Vue3+Element-Plus實(shí)現(xiàn)自定義虛擬表格滾動(dòng)的方案
背景
本文將基于 Vue3 + Element Plus,實(shí)現(xiàn)一個(gè)完全自定義的虛擬滾動(dòng)表格方案,支持不等高行、緩存高度、緩沖區(qū)渲染,并且與 el-table 解耦。
Element Plus 雖然提供了虛擬滾動(dòng),但目前還是測(cè)試(beta)階段,所以暫時(shí)先沒用了,其實(shí)當(dāng)時(shí)寫這個(gè)組件主要是為了給 Element UI 使用的。
一、如何使用
在實(shí)際業(yè)務(wù)中,如果你正在使用 Element Plus 的 el-table,又遇到了大數(shù)據(jù)量導(dǎo)致滾動(dòng)卡頓的問題,那么這個(gè)組件可以在不改 el-table 任何代碼的前提下,為表格提供一套高性能的虛擬滾動(dòng)能力。
使用方式非常簡(jiǎn)單:只需要用 VirtualListTable 包一層 el-table,并通過 change 事件接收當(dāng)前需要渲染的數(shù)據(jù)即可。
VirtualListTable負(fù)責(zé)滾動(dòng)、計(jì)算可視區(qū)和緩沖區(qū)數(shù)據(jù),el-table只負(fù)責(zé)展示當(dāng)前這一小段數(shù)據(jù),二者完全解耦。
<script>
// 渲染虛擬數(shù)據(jù)
const renderVirtualData = (data)=>{
tableData.value = data;
}
</script>
<VertualListTable :list-data="largeData" @change="renderVirtualData">
<el-table :data="tableData">
<el-table-column prop="name" label="Name" />
<el-table-column prop="address" label="Address" />
</el-table>
</VertualListTable>
二、VirtualListTable 核心實(shí)現(xiàn)解析
1?? 虛擬滾動(dòng)的關(guān)鍵變量
const start = ref(0); // 當(dāng)前起始索引 const cacheHeight = new Map(); // 行高緩存 let positions: Positions = []; // 每一行的位置 & 高度 let scrollTop = 0;
positions 的結(jié)構(gòu):
{
id: number | string,
height: number,
top: number
}
這是整個(gè)虛擬滾動(dòng)的“地圖”。
2?? 可視區(qū)數(shù)據(jù)計(jì)算(含 buffer)
const visibleCount = computed(() =>
Math.ceil(props.height / props.estimatedItemSize)
);
const visibleData = computed(() => {
const startIndex = Math.max(start.value - props.bufferCount, 0);
const endIndex = Math.min(
start.value + visibleCount.value + props.bufferCount,
props.listData.length,
);
return props.listData.slice(startIndex, endIndex);
});
為什么要 buffer?
- 防止?jié)L動(dòng)時(shí)白屏
- 提前渲染上下緩沖區(qū)域,提升體驗(yàn)
3?? 滾動(dòng)時(shí)如何快速定位起始索引(關(guān)鍵)
這里使用的是 二分查找。
const getStartIndex = (list: Positions, scrollTop: number) => {
let index = null;
let low = 0;
let high = list.length - 1;
while (low <= high) {
const mid = low + ((high - low) >> 1);
const midVal = list[mid]?.top;
if (midVal <= scrollTop) {
index = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
return index ?? 0;
};
時(shí)間復(fù)雜度從 O(n) 降到 O(log n),在大數(shù)據(jù)量下非常關(guān)鍵。
4?? 使用 transform 控制真實(shí) DOM 偏移
const setStartOffset = () => {
const index = Math.max(start.value - props.bufferCount, 0);
const offset = positions[index]?.top ?? 0;
contentRef.value!.style.transform =
`translate3d(0, ${offset}px, 0)`;
};
- 不操作
top - 使用
transform,避免觸發(fā)重排 - GPU 加速,滾動(dòng)更順滑
5?? 不等高行的核心:高度緩存 + ResizeObserver
const updateItemsSize = () => {
getNodes().forEach((node) => {
const id = getNodeId(node);
const height = node.getBoundingClientRect().height;
cacheHeight.set(id, height);
});
};
結(jié)合 ResizeObserver:
ro = new ResizeObserver(() => {
if (ignoreResize) return;
updateLayout();
});
解決的問題:
- 表格行高度動(dòng)態(tài)變化
- 文本換行、slot 變化
- 不需要強(qiáng)制固定行高
6?? 占位元素?fù)伍_滾動(dòng)條
這是虛擬滾動(dòng)中最核心的一步:DOM 只渲染幾十行,但滾動(dòng)條看起來像有幾千行
<div ref="placeholder" class="placeholder"></div>
const updateTotalHeight = () => {
const lastItem = positions.at(-1);
placeholderRef.value!.style.height =
(lastItem.top + lastItem.height) + 'px';
};
三、與 el-table 的無侵入融合
- 不改 el-table 源碼
- 只接管滾動(dòng)容器
- 所有表格功能照常使用(排序、列、樣式)
const initElement = () => {
const $wrapper = containerRef.value
?.querySelector('.el-table__body-wrapper');
const $tableBody = $wrapper
?.querySelector('.el-scrollbar');
contentRef.value?.appendChild($tableBody);
$wrapper?.appendChild(scrollBoxRef.value!);
};
四、TableBigData:保持純展示
<el-table :data="data"> <el-table-column prop="name" label="Name" /> <el-table-column prop="email" label="Email" /> </el-table>
五、總結(jié)
1. 這個(gè)方案適合什么場(chǎng)景
- 超大數(shù)據(jù)量表格(1000+)
- 行高不固定
- 老項(xiàng)目 + Element Plus
- 對(duì)滾動(dòng)性能要求高
2. 方案優(yōu)勢(shì)
- 支持不等高行
- 與 el-table 解耦
- 二分查找高性能
- buffer 防白屏
- ResizeObserver 自動(dòng)修正
以上就是基于Vue3+Element-Plus實(shí)現(xiàn)自定義虛擬表格滾動(dòng)的方案的詳細(xì)內(nèi)容,更多關(guān)于Vue3 Element-Plus虛擬表格滾動(dòng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue 父組件獲取子組件里面的data數(shù)據(jù)(實(shí)現(xiàn)步驟)
在Vue中,父組件可以通過`ref`引用子組件,并通過`$refs`屬性來訪問子組件的數(shù)據(jù),下面分步驟給大家介紹vue 父組件獲取子組件里面的data數(shù)據(jù),感興趣的朋友一起看看吧2024-06-06
vue3 中使用vue?img?cutter?圖片裁剪插件的方法
這篇文章主要介紹了vue3 中使用vue?img?cutter?圖片裁剪插件的方法,首先安裝依賴,構(gòu)建 components/ImgCutter.vue 組件,需要的朋友可以參考下2024-05-05
Ant Design moment對(duì)象和字符串之間的相互轉(zhuǎn)化教程
這篇文章主要介紹了Ant Design moment對(duì)象和字符串之間的相互轉(zhuǎn)化教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10
vue.js 實(shí)現(xiàn)點(diǎn)擊按鈕動(dòng)態(tài)添加li的方法
今天小編就為大家分享一篇vue.js 實(shí)現(xiàn)點(diǎn)擊按鈕動(dòng)態(tài)添加li的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09
Vue項(xiàng)目前后端聯(lián)調(diào)(使用proxyTable實(shí)現(xiàn)跨域方式)
這篇文章主要介紹了Vue項(xiàng)目前后端聯(lián)調(diào)(使用proxyTable實(shí)現(xiàn)跨域方式),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07
Vue項(xiàng)目中props傳值時(shí)子組件檢測(cè)不到的問題及解決
這篇文章主要介紹了Vue項(xiàng)目中props傳值時(shí)子組件檢測(cè)不到的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
vue實(shí)現(xiàn)一個(gè)滾動(dòng)條樣式
滾動(dòng)條能夠給用戶帶來極好的體驗(yàn)效果,今天通過本文給大家分享vue實(shí)現(xiàn)一個(gè)滾動(dòng)條樣式,代碼簡(jiǎn)單易懂,需要的朋友參考下吧2021-07-07

