最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React使用Canvas繪制大數(shù)據(jù)表格的實(shí)例代碼

 更新時(shí)間:2023年09月12日 11:36:51   作者:Kakarotto  
之前一直想用Canvas做表格渲染的,最近發(fā)現(xiàn)了一個(gè)很不錯(cuò)的Canvas繪圖框架Leafer,api很友好就試著寫了一下,文中有詳細(xì)的代碼示例供大家參考,感興趣的小伙伴可以自己動(dòng)手試試

之前一直想用Canvas做表格渲染的,最近發(fā)現(xiàn)了一個(gè)很不錯(cuò)的Canvas繪圖框架Leafer,api很友好就試著寫了一下。

表格渲染主要分為四個(gè)部分,1、表頭渲染,2、表格渲染,3、滾動(dòng)條渲染,4、滾動(dòng)條與表格的聯(lián)動(dòng)。

1、表頭渲染

表頭的通過(guò) JSON 格式來(lái)設(shè)置的,主要包括每列的名稱、對(duì)應(yīng)的數(shù)據(jù)的鍵值、寬度、是否需要對(duì)數(shù)據(jù)進(jìn)行二次渲染。

首先需要解決的是表頭的正確渲染,這里分為兩種情況:

1、表格列都沒(méi)有設(shè)置寬度

2、表格列有設(shè)置寬度

1.1、表格列都沒(méi)有設(shè)置寬度

1.1.1、計(jì)算表格每列的寬度

這里已知的是表格的寬度,表格的列數(shù)及表格列的名稱,解決方案如下:

文本與表格寬度比率 = 表格寬度 / 表格列文本總寬度

每列寬度 = 每列表格文本寬度 * 文本與表格寬度比率

獲取文本寬度方法:

const getTextWidth = (leafer: Leafer, text: string) => {
	return leafer.canvas.measureText(text).width;
};

1.1.2、計(jì)算表格每列的開(kāi)始坐標(biāo)

初始化表格列數(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);
});

到這里為止 表頭就可以正常渲染出來(lái)了

1.2、表格列有設(shè)置寬度

與沒(méi)有設(shè)置表格列的渲染類似

文本與表格寬度比率 = (表格寬度 - 表格設(shè)置列的總寬度) / 表格列文本總寬度

沒(méi)有設(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、滾動(dòng)條渲染

在表格渲染之前要先解決表格滾動(dòng)條和表格聯(lián)動(dòng)的問(wèn)題,根據(jù)滾動(dòng)條滾動(dòng)的距離計(jì)算表格顯示的內(nèi)容,因?yàn)槭亲岳L制表格,所以滾動(dòng)條部分不能利用瀏覽器的滾動(dòng)條。

2.1、創(chuàng)建滾動(dòng)條

滾動(dòng)條的本質(zhì)還是一個(gè) Rect,使 Rect 模擬滾動(dòng)條的行為。

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、計(jì)算滾動(dòng)條的高度、位置、樣式

2.2.1、計(jì)算滾動(dòng)條的高度

根據(jù)數(shù)據(jù)量的大小,需要調(diào)整滾動(dòng)條渲染的高度,計(jì)算方式如下:

每條數(shù)據(jù)對(duì)應(yīng)滾動(dòng)條高度 = (表格總高度 - 表頭高度) / 數(shù)據(jù)長(zhǎng)度

滾動(dòng)條高度 = 滾動(dòng)條最小高度 + 視圖內(nèi)顯示行數(shù) * 每條數(shù)據(jù)對(duì)應(yīng)滾動(dòng)條高度

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ù)量做臨時(shí)處理
	return targetHeight < viewHeight ? Math.ceil(targetHeight) : viewHeight - 10;
};

2.2.2、滾動(dòng)條的位置

滾動(dòng)條的 X 軸位置 = 表格的寬度 - 滾動(dòng)條區(qū)域的寬度

滾動(dòng)條的 Y 軸滾動(dòng)需要添加鼠標(biāo)滾輪和拖拽事件的監(jiān)聽(tīng),對(duì)滾動(dòng)條拖拽事件的監(jiān)聽(tīng)是通過(guò)監(jiān)聽(tīng)滾動(dòng)條本身,鼠標(biāo)滾輪的監(jiān)聽(tīng)需要對(duì)表格本身添加監(jiān)聽(tīng)事件

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);
});

滾動(dòng)條的最大滾動(dòng)高度 = 表格的高度 - 滾動(dòng)條高度

滾動(dòng)條的渲染是從設(shè)置的坐標(biāo)點(diǎn)開(kāi)始 + 滾動(dòng)條的高度,保證滾動(dòng)條在可視區(qū)域內(nèi),需要減去滾動(dòng)條的高度。

當(dāng)滾動(dòng)或拖拽計(jì)算值超過(guò)最大高度時(shí),為最大高度;當(dāng)滾動(dòng)或拖拽計(jì)算值小于表頭高度時(shí),為表頭高度,其他情況為滾動(dòng)條在 Y 軸方向的偏移值 + 鼠標(biāo)滾輪滾動(dòng)的距離或拖拽的距離

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 ?? "")
	);
	/**
	 * 鼠標(biāo)滾輪的滾動(dòng)向上滾動(dòng)是正值,向下是負(fù)值
	 * 這與滾動(dòng)條位置是相反的,需要在獲取滾動(dòng)距離時(shí) * -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、滾動(dòng)條的樣式

滾動(dòng)條 = 滾動(dòng)條本身 + 左右邊距

滾動(dòng)條本身寬度 = 滾動(dòng)條寬度 - 邊距 * 2

鼠標(biāo)移入移出滾動(dòng)條時(shí)會(huì)有顯隱效果,通過(guò)對(duì) Rect 添加移入移出事件來(lái)修改透明度

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、滾動(dòng)條是否顯示

當(dāng)數(shù)據(jù)長(zhǎng)度小于可視區(qū)域內(nèi)的行數(shù)時(shí),此時(shí)不需要出滾動(dòng)條,在初始化表格調(diào)用滾動(dòng)條方法添加判斷。

export const drawCanvasTable = (
	leafer: Leafer,
	columns: Column[],
	dataSource: Record<string, string>[],
	jumpIndex = 0
) => {
	// ...
	dataSource.length > viewCapacity &&
		initScrollBar(leafer, dataSource, jumpIndex);
};

3、表格渲染

3.1、初始化渲染

表格的渲染類似于表頭的渲染,表格的渲染是按照行來(lái)渲染,每行的列坐標(biāo)、寬度是和表頭一樣的,可以在表格渲染的部分保存一份。

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)容的起始位置是通過(guò)滾動(dòng)條位置來(lái)計(jì)算的,并通過(guò)滾動(dòng)條位置的變化來(lái)重新渲染表格。

滾動(dòng)距離等于最大滾動(dòng)距離時(shí),渲染的起始位置為數(shù)據(jù)總長(zhǎng)度 - 視圖可顯示的行數(shù)。

滾動(dòng)距離小于最大滾動(dòng)距離時(shí):

滾動(dòng)條偏移范圍內(nèi)需要渲染的數(shù)據(jù)單位長(zhǎng)度 = (表格高度 - 表格頭高度 - 滾動(dòng)條高度) / (數(shù)據(jù)長(zhǎng)度 - 視圖可顯示行數(shù))

渲染的起始位置 = (滾動(dòng)條位置 - 表格頭高度) / 滾動(dòng)條偏移范圍內(nèi)需要渲染的數(shù)據(jù)單位長(zhǎng)度

const setScroll = (
	leafer: Leafer,
	rect: Rect,
	e: MoveEvent | DragEvent,
	dataSource: Record<string, string>[],
	val = 1,
	scrollInfo: ScrollInfo
) => {
	// ...計(jì)算滾動(dòng)條位置代碼...
	from =
     rect.y === scrollMaxHeight
       ? dataSource.length - viewCapacity
       : Math.ceil((rect.y - headerHeight) / unitLength);
	initTableBody();
};

當(dāng)數(shù)據(jù)大于表格視圖行數(shù)時(shí),表格結(jié)束范圍 = 起始位置 + 表格視圖可以顯示的最大行數(shù),如果計(jì)算值大于數(shù)據(jù)最大長(zhǎng)度,則為數(shù)據(jù)長(zhǎng)度,否則表格的結(jié)束范圍 = 數(shù)據(jù)長(zhǎng)度

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、計(jì)算表格 Y 軸方向的偏移

因?yàn)橛?jì)算視圖內(nèi)可以顯示的表格行時(shí),會(huì)有小數(shù)的存在,這里是采用向下取整,這樣顯示的行數(shù)總高度會(huì)超過(guò)表格的可視區(qū)域高度,這時(shí)候需要對(duì)表格進(jìn)行部分偏移,使其在滾動(dòng)到底部時(shí)能夠正常顯示

  • 1、數(shù)據(jù)長(zhǎng)度小于可視區(qū)域行數(shù)時(shí),不需要偏移
  • 2、數(shù)據(jù)長(zhǎng)度大于可視區(qū)域行數(shù)時(shí)
    • 2.1 開(kāi)始位置小于需要隨滾動(dòng)條渲染的數(shù)據(jù)長(zhǎng)度時(shí),不需要偏移
    • 2.2 開(kāi)始位置大于等于需要隨滾動(dòng)條渲染的數(shù)據(jù)長(zhǎng)度時(shí):
      • 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)到指定位置

當(dāng)表格數(shù)據(jù)量大時(shí),需要能夠快速定位到某條數(shù)據(jù),當(dāng)接收到需要跳轉(zhuǎn)到的行時(shí),該數(shù)據(jù)為起始位置,重新執(zhí)行渲染表格一系列方法,因?yàn)樵跐L動(dòng)條初始化時(shí)修改了滾動(dòng)條的初始高度,所以在跳轉(zhuǎn)操作時(shí)不應(yīng)該修改表格行的高度

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ù)表格的詳細(xì)內(nèi)容,更多關(guān)于React Canvas繪制表格的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • react中Suspense的使用詳解

    react中Suspense的使用詳解

    這篇文章主要介紹了react中Suspense的使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • ReactNative環(huán)境搭建的教程

    ReactNative環(huán)境搭建的教程

    這篇文章主要介紹了ReactNative環(huán)境搭建的教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • React使用useEffect解決setState副作用詳解

    React使用useEffect解決setState副作用詳解

    這篇文章主要為大家介紹了React使用useEffect解決setState副作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • react使用useImperativeHandle示例詳解

    react使用useImperativeHandle示例詳解

    這篇文章主要為大家介紹了react使用useImperativeHandle示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • React-router?v6在Class組件和非組件代碼中的正確使用

    React-router?v6在Class組件和非組件代碼中的正確使用

    這篇文章主要介紹了React-router?v6在Class組件和非組件代碼中的正確使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • React-Native中props具體使用詳解

    React-Native中props具體使用詳解

    本篇文章主要介紹了React-Native中props具體使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • react native 獲取地理位置的方法示例

    react native 獲取地理位置的方法示例

    這篇文章主要介紹了react native 獲取地理位置的方法示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • 一文掌握React?組件樹(shù)遍歷技巧

    一文掌握React?組件樹(shù)遍歷技巧

    這篇文章主要為大家介紹了React?組件樹(shù)遍歷技巧的掌握,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Remix后臺(tái)開(kāi)發(fā)之remix-antd-admin配置過(guò)程

    Remix后臺(tái)開(kāi)發(fā)之remix-antd-admin配置過(guò)程

    這篇文章主要為大家介紹了Remix后臺(tái)開(kāi)發(fā)之remix-antd-admin配置過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn)的方法

    React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn)的方法

    這篇文章主要介紹了React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn),主要有兩種跳轉(zhuǎn)方式一種是編程式跳轉(zhuǎn)另一種是NavLink鏈接式跳轉(zhuǎn),每種方式通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08

最新評(píng)論

伽师县| 资溪县| 伊川县| 阿坝| 微博| 肃宁县| 淅川县| 镇赉县| 内黄县| 红桥区| 巢湖市| 建湖县| 平邑县| 卢龙县| 安阳县| 宝山区| 伊川县| 武宁县| 贵港市| 雷州市| 项城市| 鹤庆县| 宣威市| 开平市| 望奎县| 枣强县| 龙里县| 栾川县| 普兰店市| 繁昌县| 杨浦区| 彩票| 河源市| 张家口市| 台北市| 高安市| 墨竹工卡县| 登封市| 凌云县| 扶风县| 淅川县|