react、worker、canvas、offscreencanvas demo

66 阅读1分钟

react vite 基础worker

vitejs.cn/guide/asset…

image.png

index.ts

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;

index.worker.ts

// 保存点击记录和渲染状态
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++;

  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
  }, [bitmap]);
}

// 监听主线程消息
self.onmessage = (e: MessageEvent) => {
  const data = e.data;
  switch (data.type) {
    case 'init':
      init(data.canvas, data.width, data.height);
      break;
    case 'click':
      handleClick(data?.position?.x, data?.position?.y);
      break;
  }
};