canvas web worker 离屏渲染,避免UI阻塞
import { useEffect, useRef } from 'react';
import MyWorker from './index.worker.ts?worker';
const ListDemo = () => {
const worker = new MyWorker();
const canvasOneRef = useRef<HTMLCanvasElement>(null); // offscreen canvas
const canvasOneRef1 = useRef<HTMLCanvasElement>(null); // render canvas
useEffect(() => {
const canvas = canvasOneRef.current;
const displayCanvas = canvasOneRef1.current;
if (!canvas || !displayCanvas) return; // 添加null检查
// 设置canvas尺寸
const dpr = window.devicePixelRatio;
canvas.width = 300 * dpr;
canvas.height = 300 * dpr;
canvas.style.width = '300px';
canvas.style.height = '300px';
const offScreenCanvas = canvas.transferControlToOffscreen();
worker.postMessage({
type: 'init',
canvas: offScreenCanvas,
width: 600,
height: 600,
}, [offScreenCanvas]);
const handleCanvasClick = (e: MouseEvent) => { // 明确事件类型
const rect = displayCanvas.getBoundingClientRect();
const pos = {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
};
console.log(pos);
worker.postMessage({
type: 'click',
position: pos,
});
};
displayCanvas.addEventListener('click', handleCanvasClick);
worker.onmessage = (e: MessageEvent) => { // 明确事件类型
const { data } = e;
console.log(data);
if (data.type === 'render') {
if (displayCanvas && data.bitmap) {
const ctx = displayCanvas.getContext('2d');
if (ctx) {
ctx.drawImage(data.bitmap, 0, 0);
data.bitmap.close();
}
}
}
};
return () => {
canvas.removeEventListener('click', handleCanvasClick);
};
}, []);
return (
<div>
<canvas ref={canvasOneRef} width={300} height={300} style={{ display: 'none' }}></canvas>
<canvas ref={canvasOneRef1} width={300} height={300}></canvas>
</div>
);
};
export default ListDemo;
// 保存点击记录和渲染状态
let clickCount = 0;
let ctx: OffscreenCanvasRenderingContext2D;
// 初始化画布
function init(canvas: OffscreenCanvas, width: number, height: number) {
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext('2d')!;
render(); // 初始渲染
}
// 处理点击事件
function handleClick(x: number, y: number) {
clickCount = clickCount + 1;
render(x, y); // 带坐标重新渲染
}
// 渲染函数
function render(clickX?: number, clickY?: number) {
// 清空画布
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// 绘制背景
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// 绘制当前点击次数
ctx.fillStyle = '#333';
ctx.font = '24px Arial';
ctx.textAlign = 'center';
ctx.fillText(`已点击: ${clickCount}次`, ctx.canvas.width / 2, 40);
// 如果有新点击,绘制标记
if (clickX !== undefined && clickY !== undefined) {
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
ctx.arc(clickX, clickY, 10, 0, Math.PI * 2);
ctx.fill();
// 在点击点旁边显示序号
ctx.fillStyle = '#fff';
ctx.font = '12px Arial';
ctx.fillText(clickCount.toString(), clickX, clickY - 15);
}
// 将渲染结果传回主线程
const bitmap = ctx.canvas.transferToImageBitmap();
(self as any).postMessage({
type: 'render',
bitmap,
}, [bitmap]);
}
// 监听主线程消息
self.onmessage = (e: MessageEvent) => {
const { data } = e;
switch (data.type) {
case 'init':
init(data.canvas, data.width, data.height);
break;
case 'click':
handleClick(data?.position?.x, data?.position?.y);
break;
}
};