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

React Canvas绘图:鼠标抬起时获取画布内容转图片发送方案

React Canvas绘图应用抬笔捕获画布上传实现方案

问题根因

你之前尝试使用的mousechange不是标准DOM鼠标事件,Canvas元素无法识别该事件,自然不会触发绑定逻辑。实际上你现有代码已经绑定了正确的onMouseUp事件(对应鼠标抬起、单次绘制结束的时机),只需要在对应的处理函数中补充画布转图片、上传的逻辑即可。
另外你现有代码存在两个小隐患需要先修复:

  • 监听窗口resize的useEffect没有传入依赖数组,会导致组件每次渲染都重复绑定resize事件,造成内存泄漏
  • Canvas的height属性传入了字符串720px,Canvas原生属性仅接受数字类型值,传字符串会导致画布分辨率异常、绘制内容模糊

具体实现步骤

1. 补充核心转码上传逻辑

在你已有的endDrawing函数中插入画布转图片、发送请求的代码即可,两种转码方式可按需选择:

  • 若需要base64格式图片:调用canvas.toDataURL('image/png')直接获取base64字符串
  • 若需要上传到服务端:推荐调用canvas.toBlob()获取二进制Blob对象,搭配FormData上传性能更好

2. 修复现有代码隐患

  • 给resize监听的useEffect添加空依赖数组[],保证仅在组件挂载时绑定一次事件
  • 将Canvas的height属性从字符串720px改为数字720

修改后的完整DrawingSEC组件代码

import { useEffect, useRef, useState } from "react";
import Menu from "./Menu";
import "./App.css";
import { useStateContext } from '../../contexts/ContextProvider';

const DrawingSEC = () => {
    const canvasRef = useRef(null);
    const ctxRef = useRef(null);
    const [isDrawing, setIsDrawing] = useState(false);
    const [lineWidth, setLineWidth] = useState(2);
    const [lineColor, setLineColor] = useState("black");
    const {screenSize, setScreenSize} = useStateContext();
  
    // 修复:添加空依赖数组,避免重复绑定事件
    useEffect(() => {
      const handleResize = () => setScreenSize(window.innerWidth);
      window.addEventListener('resize', handleResize);
      handleResize();
      return () => window.removeEventListener('resize', handleResize);
    }, []);
    
    // 画布初始化
    useEffect(() => {
      const canvas = canvasRef.current;
      const ctx = canvas.getContext("2d");
      ctx.lineCap = "round";
      ctx.lineJoin = "round";
      ctx.strokeStyle = lineColor;
      ctx.lineWidth = lineWidth;
      ctxRef.current = ctx;
    }, [lineColor, lineWidth]);
    
    // 开始绘制
    const startDrawing = (e) => {
      ctxRef.current.beginPath();
      ctxRef.current.moveTo(
        e.nativeEvent.offsetX, 
        e.nativeEvent.offsetY
      );
      setIsDrawing(true);
    };
    
    // 结束绘制(鼠标抬起触发)
    const endDrawing = () => {
      ctxRef.current.closePath();
      setIsDrawing(false);

      // 抬笔后捕获画布内容
      const canvas = canvasRef.current;
      // 方案1:转base64格式,按需开启
      // const imageBase64 = canvas.toDataURL('image/png');
      
      // 方案2:转Blob二进制格式(推荐用于上传)
      canvas.toBlob(async (blob) => {
        if (!blob) return;
        // 构造上传请求
        const formData = new FormData();
        formData.append('drawingImage', blob, `drawing_${Date.now()}.png`);
        // 替换成你的实际接口地址
        await fetch('/api/upload-drawing', {
          method: 'POST',
          body: formData
        });
      }, 'image/png', 0.9);
    };
    
    // 绘制过程
    const draw = (e) => {
      if (!isDrawing) return;
      ctxRef.current.lineTo(
        e.nativeEvent.offsetX, 
        e.nativeEvent.offsetY
      );
      ctxRef.current.stroke();
    };

  return (
    <div className="App">
      <div className="draw-area">
        <Menu
          setLineColor={setLineColor}
          setLineWidth={setLineWidth}
          lineWidth={lineWidth}
        />
        <canvas
          onMouseDown={startDrawing}
          onMouseUp={endDrawing}
          onMouseMove={draw}
          // 补充鼠标移出画布时也触发结束绘制,避免鼠标拖到画布外抬笔导致的状态异常
          onMouseLeave={endDrawing}
          ref={canvasRef}
          width={screenSize-300}
          // 修复:height传数字类型
          height={720}
        />
      </div>
    </div>
  )
}

export default DrawingSEC

补充说明

  • 上述代码额外给Canvas添加了onMouseLeave={endDrawing}事件,避免用户按住鼠标拖到画布区域外才抬起时,isDrawing状态没有被重置为false,导致下次鼠标移入画布时自动开始绘制的bug
  • 每次用户抬笔(不管是在画布内抬笔,还是拖出画布后抬笔)都会触发一次画布捕获和上传逻辑,完全匹配每次绘制结束重复触发,直到用户完全停止绘图的需求
  • 如果你需要在用户完全停止操作一段时间后再上传(而不是每抬一次笔就传一次),可以加个防抖函数,延迟执行上传逻辑即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 19:54:15