React使用Canvas繪制大數(shù)據(jù)表格的實例代碼
之前一直想用Canvas做表格渲染的,最近發(fā)現(xiàn)了一個很不錯的Canvas繪圖框架Leafer,api很友好就試著寫了一下。
表格渲染主要分為四個部分,1、表頭渲染,2、表格渲染,3、滾動條渲染,4、滾動條與表格的聯(lián)動。
1、表頭渲染
表頭的通過 JSON 格式來設(shè)置的,主要包括每列的名稱、對應的數(shù)據(jù)的鍵值、寬度、是否需要對數(shù)據(jù)進行二次渲染。
首先需要解決的是表頭的正確渲染,這里分為兩種情況:
1、表格列都沒有設(shè)置寬度
2、表格列有設(shè)置寬度
1.1、表格列都沒有設(shè)置寬度
1.1.1、計算表格每列的寬度
這里已知的是表格的寬度,表格的列數(shù)及表格列的名稱,解決方案如下:
文本與表格寬度比率 = 表格寬度 / 表格列文本總寬度
每列寬度 = 每列表格文本寬度 * 文本與表格寬度比率
獲取文本寬度方法:
const getTextWidth = (leafer: Leafer, text: string) => {
return leafer.canvas.measureText(text).width;
};1.1.2、計算表格每列的開始坐標
初始化表格列數(shù)據(jù)結(jié)構(gòu),給表格列添加 width 字符
const thList = columns.map((item) => {
return {
...item,
width: item.width
? item.width
: Math.floor(getTextWidth(leafer, item.title) * widthRatio),
};
});循環(huán)遍歷表格列 渲染表頭
const group = new Group({ x, y, id: "tableHeader" });
thList.forEach((th, index) => {
const midLength = thList.slice(0, index).reduce((acc, cur) => {
return acc + cur.width;
}, 0);
const x = index === 0 ? 0 : midLength - index;
const rect = new Rect({
x,
y: 0,
width: th.width,
height: initParams.headerHeight,
fill: "#417A77",
stroke: "#b4c9fb",
});
group.add(rect);
const text = new Text({
x,
y,
width: th.width,
textAlign: "center",
height: headerHeight,
verticalAlign: "middle",
fill: "#000000",
text: th.title,
fontSize,
});
group.add(text);
});到這里為止 表頭就可以正常渲染出來了
1.2、表格列有設(shè)置寬度
與沒有設(shè)置表格列的渲染類似
文本與表格寬度比率 = (表格寬度 - 表格設(shè)置列的總寬度) / 表格列文本總寬度
沒有設(shè)置寬度的列寬度 = 每列表格文本寬度 * 文本與表格寬度比率
const noSetWidthColWidth = columns.reduce((acc, cur) => {
if (cur.width) {
return "";
}
return acc + cur.title;
}, "");
const textWidth = getTextWidth(leafer, noSetWidthColWidth);
const setColWidthSum = columns.reduce((acc, cur) => {
if (cur.width) {
return acc + cur.width;
}
return acc;
}, 0);
const widthRatio = (width - setColWidthSum) / textWidth;渲染方式同上,最后掛載到 leafer 中完成渲染
2、滾動條渲染
在表格渲染之前要先解決表格滾動條和表格聯(lián)動的問題,根據(jù)滾動條滾動的距離計算表格顯示的內(nèi)容,因為是自繪制表格,所以滾動條部分不能利用瀏覽器的滾動條。
2.1、創(chuàng)建滾動條
滾動條的本質(zhì)還是一個 Rect,使 Rect 模擬滾動條的行為。
const rect = new Rect({
x: width - scrollBar.width,
y: initParams.headerHeight,
width: scrollBar.width - scrollBar.margin * 2,
height: scrollBar.height,
fill: "rgba(133,117,85, 0.8)",
cornerRadius: 10,
id: "scrollBar",
zIndex: scrollBar.zIndex,
});2.2、計算滾動條的高度、位置、樣式
2.2.1、計算滾動條的高度
根據(jù)數(shù)據(jù)量的大小,需要調(diào)整滾動條渲染的高度,計算方式如下:
每條數(shù)據(jù)對應滾動條高度 = (表格總高度 - 表頭高度) / 數(shù)據(jù)長度
滾動條高度 = 滾動條最小高度 + 視圖內(nèi)顯示行數(shù) * 每條數(shù)據(jù)對應滾動條高度
const computedScrollBarHeight = (
leafer: Leafer,
dataSource: Record<string, string>[],
jumpIndex = 0
) => {
const { height } = leafer;
const { viewHeight, viewCapacity } = getViewInfo(leafer);
const unitLength = (height - initParams.headerHeight) / dataSource.length;
if (jumpIndex) {
return initParams.scrollBar.height;
}
const targetHeight = initParams.scrollBar.height + viewCapacity * unitLength;
// 小數(shù)據(jù)量做臨時處理
return targetHeight < viewHeight ? Math.ceil(targetHeight) : viewHeight - 10;
};2.2.2、滾動條的位置
滾動條的 X 軸位置 = 表格的寬度 - 滾動條區(qū)域的寬度
滾動條的 Y 軸滾動需要添加鼠標滾輪和拖拽事件的監(jiān)聽,對滾動條拖拽事件的監(jiān)聽是通過監(jiān)聽滾動條本身,鼠標滾輪的監(jiān)聽需要對表格本身添加監(jiān)聽事件
leafer.on(MoveEvent.MOVE, function (e) {
setScroll(leafer, rect, e, dataSource, -0.1, scrollParams);
});
rect.on(DragEvent.DRAG, function (e) {
setScroll(leafer, rect, e, dataSource, 1, scrollParams);
});滾動條的最大滾動高度 = 表格的高度 - 滾動條高度
滾動條的渲染是從設(shè)置的坐標點開始 + 滾動條的高度,保證滾動條在可視區(qū)域內(nèi),需要減去滾動條的高度。
當滾動或拖拽計算值超過最大高度時,為最大高度;當滾動或拖拽計算值小于表頭高度時,為表頭高度,其他情況為滾動條在 Y 軸方向的偏移值 + 鼠標滾輪滾動的距離或拖拽的距離
const setScroll = (
leafer: Leafer,
rect: Rect,
e: MoveEvent | DragEvent,
dataSource: Record<string, string>[],
val = 1,
scrollInfo: ScrollInfo
) => {
const {
scrollMaxHeight,
headerHeight,
height,
scrollBar,
viewCapacity,
unitLength,
} = scrollInfo;
leafer.children = leafer.children.filter((item) =>
fixedGroup.includes(item.id ?? "")
);
/**
* 鼠標滾輪的滾動向上滾動是正值,向下是負值
* 這與滾動條位置是相反的,需要在獲取滾動距離時 * -1
* */
rect.y =
rect.y + e.moveY * val >= scrollMaxHeight
? scrollMaxHeight
: rect.y + e.moveY * val < headerHeight
? headerHeight
: rect.y + e.moveY * val;
};2.2.3、滾動條的樣式
滾動條 = 滾動條本身 + 左右邊距
滾動條本身寬度 = 滾動條寬度 - 邊距 * 2
鼠標移入移出滾動條時會有顯隱效果,通過對 Rect 添加移入移出事件來修改透明度
rect.on(PointerEvent.ENTER, (e) => {
e.target.fill = "rgba(133,117,85, 1)";
});
rect.on(PointerEvent.LEAVE, (e) => {
e.target.fill = "rgba(133,117,85, 0.8)";
});2.3、滾動條是否顯示
當數(shù)據(jù)長度小于可視區(qū)域內(nèi)的行數(shù)時,此時不需要出滾動條,在初始化表格調(diào)用滾動條方法添加判斷。
export const drawCanvasTable = (
leafer: Leafer,
columns: Column[],
dataSource: Record<string, string>[],
jumpIndex = 0
) => {
// ...
dataSource.length > viewCapacity &&
initScrollBar(leafer, dataSource, jumpIndex);
};3、表格渲染
3.1、初始化渲染
表格的渲染類似于表頭的渲染,表格的渲染是按照行來渲染,每行的列坐標、寬度是和表頭一樣的,可以在表格渲染的部分保存一份。
thList.forEach((th, index) => {
const midLength = thList.slice(0, index).reduce((acc, cur) => {
return acc + cur.width;
}, 0);
const x = index === 0 ? 0 : midLength - index;
tableHeaderInfo[th.dataIndex] = {
x,
width: th.width,
};
// ...省略渲染部分...
});3.2、獲取渲染的范圍
表格渲染內(nèi)容的起始位置是通過滾動條位置來計算的,并通過滾動條位置的變化來重新渲染表格。
滾動距離等于最大滾動距離時,渲染的起始位置為數(shù)據(jù)總長度 - 視圖可顯示的行數(shù)。
滾動距離小于最大滾動距離時:
滾動條偏移范圍內(nèi)需要渲染的數(shù)據(jù)單位長度 = (表格高度 - 表格頭高度 - 滾動條高度) / (數(shù)據(jù)長度 - 視圖可顯示行數(shù))
渲染的起始位置 = (滾動條位置 - 表格頭高度) / 滾動條偏移范圍內(nèi)需要渲染的數(shù)據(jù)單位長度
const setScroll = (
leafer: Leafer,
rect: Rect,
e: MoveEvent | DragEvent,
dataSource: Record<string, string>[],
val = 1,
scrollInfo: ScrollInfo
) => {
// ...計算滾動條位置代碼...
from =
rect.y === scrollMaxHeight
? dataSource.length - viewCapacity
: Math.ceil((rect.y - headerHeight) / unitLength);
initTableBody();
};當數(shù)據(jù)大于表格視圖行數(shù)時,表格結(jié)束范圍 = 起始位置 + 表格視圖可以顯示的最大行數(shù),如果計算值大于數(shù)據(jù)最大長度,則為數(shù)據(jù)長度,否則表格的結(jié)束范圍 = 數(shù)據(jù)長度
const computedViewBoundary = (
i: number,
start: number,
viewCapacity: number,
dataSource: Record<string, string>[]
) => {
if (dataSource.length > viewCapacity) {
return (i < start + viewCapacity && start + viewCapacity <= dataSource.length);
} else {
return i < dataSource.length;
}
};3.3、計算表格 Y 軸方向的偏移
因為計算視圖內(nèi)可以顯示的表格行時,會有小數(shù)的存在,這里是采用向下取整,這樣顯示的行數(shù)總高度會超過表格的可視區(qū)域高度,這時候需要對表格進行部分偏移,使其在滾動到底部時能夠正常顯示
- 1、數(shù)據(jù)長度小于可視區(qū)域行數(shù)時,不需要偏移
- 2、數(shù)據(jù)長度大于可視區(qū)域行數(shù)時
- 2.1 開始位置小于需要隨滾動條渲染的數(shù)據(jù)長度時,不需要偏移
- 2.2 開始位置大于等于需要隨滾動條渲染的數(shù)據(jù)長度時:
- 2.2.1 如果表格行高可以被視圖高度整除,不需要偏移
- 2.2.2 如果表格行高不可以被視圖高度整除,偏移值 = 表格頭高度 - (表格行高 - 視圖高度 % 行高)
const computedTableOffset = (
viewCapacity: number,
headerHeight: number,
viewHeight: number,
rowHeight: number
) => {
return globalDataSource.length > viewCapacity
? from < Math.floor(globalDataSource.length - viewCapacity)
? headerHeight
: headerHeight -
(viewHeight % rowHeight ? rowHeight - (viewHeight % rowHeight) : 0)
: headerHeight;
};4、跳轉(zhuǎn)到指定位置
當表格數(shù)據(jù)量大時,需要能夠快速定位到某條數(shù)據(jù),當接收到需要跳轉(zhuǎn)到的行時,該數(shù)據(jù)為起始位置,重新執(zhí)行渲染表格一系列方法,因為在滾動條初始化時修改了滾動條的初始高度,所以在跳轉(zhuǎn)操作時不應該修改表格行的高度
useEffect(() => {
if (canvasDom.current) {
const leafer = new Leafer({
view: canvasDom.current,
width: 500,
height: 800,
move: { dragOut: false },
type: "user",
});
drawCanvasTable(leafer, columns, dataSource, jumpIndex);
}
}, [columns, dataSource, jumpIndex]);if (jumpIndex) {
return initParams.scrollBar.height;
}代碼地址:
https://stackblitz.com/edit/vitejs-vite-emryft?file=src%2FApp.tsx
以上就是React使用Canvas繪制大數(shù)據(jù)表格的詳細內(nèi)容,更多關(guān)于React Canvas繪制表格的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React使用useEffect解決setState副作用詳解
這篇文章主要為大家介紹了React使用useEffect解決setState副作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
react使用useImperativeHandle示例詳解
這篇文章主要為大家介紹了react使用useImperativeHandle示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
React-router?v6在Class組件和非組件代碼中的正確使用
這篇文章主要介紹了React-router?v6在Class組件和非組件代碼中的正確使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
Remix后臺開發(fā)之remix-antd-admin配置過程
這篇文章主要為大家介紹了Remix后臺開發(fā)之remix-antd-admin配置過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04
React-RouterV6+AntdV4實現(xiàn)Menu菜單路由跳轉(zhuǎn)的方法
這篇文章主要介紹了React-RouterV6+AntdV4實現(xiàn)Menu菜單路由跳轉(zhuǎn),主要有兩種跳轉(zhuǎn)方式一種是編程式跳轉(zhuǎn)另一種是NavLink鏈接式跳轉(zhuǎn),每種方式通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-08-08

