You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React TypeScript实现Canvas鼠标/触摸绘制的问题求助

解决方案:React TypeScript 重构 Canvas 绘图组件

下面是针对你遇到的三个问题的修复方案,以及重构后的完整代码:


1. 事件类型的正确定义

针对不同交互场景,直接使用 TypeScript 内置的事件类型:

  • 鼠标事件:MouseEvent
  • 触摸事件:TouchEvent(注意触摸坐标需从event.touches[0]提取)
  • 窗口 resize 事件:UIEvent

替换所有any类型,提升代码的类型安全性。

2. 修复 getContext 类型错误

不要通过 props 传递 canvas 元素,改用 React 的useRef钩子获取 canvas DOM 节点,并明确指定类型为HTMLCanvasElement,这样就能正确调用getContext方法。

3. React 中事件绑定的最优方案

避免直接在组件函数体中调用document.addEventListener(重复渲染会导致重复绑定),改用useEffect钩子统一管理事件:

  • 在组件挂载时添加监听
  • 组件卸载时自动移除监听
  • 符合 React 生命周期管理逻辑,避免内存泄漏

重构后的完整代码

import React, { useRef, useEffect } from "react";

const styles = {
  canvas: {
    width: "100vw",
    height: "100vh",
    cursor: "url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\"  width=\"40\" height=\"48\" viewport=\"0 0 100 100\" style=\"fill:black;font-size:24px;\"><text y=\"50%\">✍️</text></svg>') 5 25, auto",
  }
};

function App() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const coordRef = useRef({ x: 0, y: 0 });
  let context: CanvasRenderingContext2D | null = null;

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    context = canvas.getContext("2d");
    resizeCanvas();

    // 鼠标事件处理
    const handleMouseDown = (e: MouseEvent) => {
      document.addEventListener("mousemove", handleDraw);
      reposition(e);
    };
    const handleMouseUp = () => {
      document.removeEventListener("mousemove", handleDraw);
    };

    // 触摸事件处理
    const handleTouchStart = (e: TouchEvent) => {
      document.addEventListener("touchmove", handleDraw);
      reposition(e.touches[0]);
    };
    const handleTouchEnd = () => {
      document.removeEventListener("touchmove", handleDraw);
    };

    // 窗口 resize 处理
    const handleResize = () => resizeCanvas();

    // 绑定事件
    document.addEventListener("mousedown", handleMouseDown);
    document.addEventListener("mouseup", handleMouseUp);
    document.addEventListener("touchstart", handleTouchStart);
    document.addEventListener("touchend", handleTouchEnd);
    window.addEventListener("resize", handleResize);

    // 组件卸载时清理事件
    return () => {
      document.removeEventListener("mousedown", handleMouseDown);
      document.removeEventListener("mouseup", handleMouseUp);
      document.removeEventListener("touchstart", handleTouchStart);
      document.removeEventListener("touchend", handleTouchEnd);
      window.removeEventListener("resize", handleResize);
      document.removeEventListener("mousemove", handleDraw);
      document.removeEventListener("touchmove", handleDraw);
    };
  }, []);

  // 调整画布尺寸
  const resizeCanvas = () => {
    if (!canvasRef.current) return;
    canvasRef.current.width = window.innerWidth;
    canvasRef.current.height = window.innerHeight;
  };

  // 更新绘制坐标
  const reposition = (event: MouseEvent | Touch) => {
    if (!canvasRef.current) return;
    coordRef.current.x = event.clientX - canvasRef.current.offsetLeft;
    coordRef.current.y = event.clientY - canvasRef.current.offsetTop;
  };

  // 绘制逻辑
  const handleDraw = (event: MouseEvent | TouchEvent) => {
    if (!context) return;
    const targetEvent = event instanceof TouchEvent ? event.touches[0] : event;
    
    context.beginPath();
    context.lineWidth = 5;
    context.lineCap = "round";
    context.strokeStyle = "#ACD3ED";
    context.moveTo(coordRef.current.x, coordRef.current.y);
    reposition(targetEvent);
    context.lineTo(coordRef.current.x, coordRef.current.y);
    context.stroke();
  };

  return <canvas ref={canvasRef} style={styles.canvas} />;
}

export default App;

关键修复点说明

  • 用useRef<HTMLCanvasElement>(null)获取画布节点,彻底解决getContext类型错误
  • 所有事件参数明确指定类型,消除any的类型隐患
  • 通过useEffect统一管理事件生命周期,避免重复绑定和内存泄漏
  • 分离鼠标/触摸事件逻辑,修复触摸坐标获取问题
  • 用coordRef存储绘制坐标,避免组件重渲染导致的状态重置

内容的提问来源于stack exchange,提问作者user18943198

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 10:01:19