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
相关产品推荐
相关产品推荐

