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

React Canvas绘图捕获绘制区域时初始坐标undefined问题求解

Canvas绘制初始坐标返回undefined问题修复

问题核心原因

  • 数据结构读写不匹配:createElement返回的结构是{ points: [{x, y}] },但draw函数中直接从元素顶层解构x1、y1属性,这两个属性根本不存在,自然返回undefined。
  • React状态异步更新陷阱:draw是高频触发的事件回调,直接读取elements状态会拿到渲染闭包里的旧值,不仅会拿错索引,还会丢失实时更新的坐标点。
  • 笔迹点存储逻辑缺失:鼠标移动时没有把新的坐标追加到对应线条的points数组中,仅重复创建只有起点的元素,无法拿到完整笔迹。
  • Canvas属性传值错误:canvas标签的height属性传入了带单位的字符串720px,原生canvas的宽高属性仅接受无单位数字,会导致画布拉伸、坐标计算偏移。
  • 事件监听逻辑泄漏:监听窗口resize的useEffect没有传入依赖数组,每次组件渲染都会重复绑定事件,造成性能问题。

可行修复方案

  • 新增currentElementref存储正在绘制的线条实时数据,避开状态异步更新的闭包问题,绘制结束后再同步到elements状态。
  • 统一笔迹数据结构,所有坐标点都存入对应线条的points数组,鼠标移动时直接追加新坐标到当前线条的points列表。
  • 修正canvas宽高属性传值,全部传入无单位的数值类型,避免坐标偏移。
  • 绘制过程中实时更新当前线条的点数据,绘制结束后统一打印完整的笔迹坐标数组。
  • 给resize监听的useEffect添加空依赖数组,仅在组件挂载时绑定一次事件。

修正后代码

DrawingSEC 组件

import { useEffect, useRef, useState } from "react";
import rough from 'roughjs/bundled/rough.esm';
import Menu from "./Menu";
import "./App.css";

const DrawingSEC = () => {
  const [elements, setElements] = useState([]);
  const canvasRef = useRef(null);
  const ctxRef = useRef(null);
  // 存正在绘制的当前元素,避开state异步问题
  const currentElementRef = useRef(null);
  const [isDrawing, setIsDrawing] = useState(false);
  const [lineWidth, setLineWidth] = useState(5);
  const [lineColor, setLineColor] = useState("black");
  const [selectedElement, setSelectedElement] = useState(null);
  const [screenSize, setScreenSize] = useState(window.innerWidth);

  // 创建线条元素,统一存points数组
  const createLine = (x, y) => {
    return {
      points: [{ x, y }]
    };
  };

  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 = ({ nativeEvent }) => {
    const { offsetX, offsetY } = nativeEvent;
    // 初始化当前线条
    const newLine = createLine(offsetX, offsetY);
    currentElementRef.current = newLine;

    ctxRef.current.beginPath();
    ctxRef.current.moveTo(offsetX, offsetY);
    ctxRef.current.lineTo(offsetX, offsetY);
    ctxRef.current.stroke();
    setIsDrawing(true);
    nativeEvent.preventDefault();
    setSelectedElement({x: offsetX, y: offsetY});
  };

  const draw = ({ nativeEvent }) => {
    if (!isDrawing || !currentElementRef.current) return;
    const { offsetX, offsetY } = nativeEvent;
    
    // 追加新点到当前线条
    currentElementRef.current.points.push({x: offsetX, y: offsetY});
    
    ctxRef.current.lineTo(offsetX, offsetY);
    ctxRef.current.stroke();
    nativeEvent.preventDefault();
  };

  const endDrawing = () => {
    if (!currentElementRef.current) return;
    ctxRef.current.closePath();
    // 绘制结束后把完整线条存入elements
    setElements(prev => [...prev, currentElementRef.current]);
    console.log("所有笔迹坐标:", [...elements, currentElementRef.current]);
    currentElementRef.current = null;
    setIsDrawing(false);
  };

  const SetToDraw = () => {
    ctxRef.current.globalCompositeOperation = "source-over";
  }
  const SetToErase = () => {
    ctxRef.current.globalCompositeOperation = "destination-out";
  }

  return (
    <div className="App">
      <div className="draw-area">
        <Menu
          setLineColor={setLineColor}
          setLineWidth={setLineWidth}
          lineWidth={lineWidth}
        />
        <div>
          <button
            className="text-xl rounded-full p-3 hover:bg-light-gray"
            onClick={SetToDraw}>
            pen
          </button>
          <button
            className="text-xl rounded-full p-3 hover:bg-light-gray"
            onClick={SetToErase}>
            erase
          </button>
        </div>
        <canvas
          onMouseDown={startDrawing}
          onMouseUp={endDrawing}
          onMouseMove={draw}
          onMouseLeave={endDrawing}
          ref={canvasRef}
          width={screenSize - 300}
          height={720}
        />
      </div>
    </div>
  )
}

export default DrawingSEC
import React from "react";
import "./App.css";

const Menu = ({ setLineColor, setLineWidth,lineWidth }) => {
return (
    <div className="Menu">
    <label>pen color   </label>
    <input
        type="color"
        onChange={(e) => {
        setLineColor(e.target.value);
        }}
        style={{borderRadius:"50%"}}
    />
    <label>Pen Size  </label>
    <input
        type="range"
        min="3"
        max="20"
        value={lineWidth}
        onChange={(e) => {
        setLineWidth(e.target.value);
        }}
    />
    
    </div>
);
};

export default Menu;

效果说明

修复后绘制结束打印elements数组时,可以拿到每一条笔迹的所有点坐标,包含初始起点和所有移动过程中的路径点,不会再出现初始坐标undefined的问题,后续做笔迹识别、绘制区域捕获都可以直接基于elements里的points数据处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 22:36:23