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

React Canvas点击绘制矩形位置偏移问题求助

React Canvas点击位置与绘制矩形位置不符问题排查与解决

问题描述

我用React开发了一个应用,需求是用户点击Canvas指定位置时,在对应位置绘制矩形,但当前矩形显示位置与点击位置不符。已检查代码及CSS样式,仍无法定位问题根源。应用包含TimeLineApp组件作为Canvas容器,TimeLineAppCanvas组件实现Canvas元素、处理点击事件并通过getContext方法绘制矩形,同时为Canvas容器(#TimeLineApp)和Canvas元素定义了CSS样式,怀疑问题出在CSS规则或坐标计算逻辑上。

相关代码

MousePosition组件代码

import { useEffect, useState } from "react";

function MousePosition(global){
const [mouseCoords, setMouseCoords] = useState({
    x: 0,
    y: 0,
})

const handleCursorMovement = (e) => {
    let rect = e.target.getBoundingClientRect();
    setMouseCoords({
        x: ((e.clientX)- (rect.left)),
        y: (e.clientY - rect.top) 
    });
    console.log( 'Canvas',  mouseCoords.x, mouseCoords.y)
    console.log(e.target.getBoundingClientRect())
    console.log( 'Actual',  e.clientX);
};

useEffect(() => {
    if(global){
    window.addEventListener("mousemove", handleCursorMovement);
    }
    return () => {
    window.removeEventListener("mousemove", handleCursorMovement);
         };
    }, [global]);
   
return [mouseCoords, handleCursorMovement];
}

export default MousePosition;

TimeLineAppCanvas组件代码

import React, {useRef, useEffect} from 'react'
import MousePosition from './MouseCoordinates';

const TimeLineAppCanvas = props => {
    const canvasRef = useRef(null);
    const [coords, handleCoords] = MousePosition(true);

    return <canvas ref={canvasRef} {...props}
    onClick={(e) => {
      handleCoords((e));
      if (canvasRef.current) {
        const ctx = canvasRef.current.getContext("2d");
        ctx.strokeStyle = "#0095DD";
        ctx?.strokeRect(coords.x, coords.y, 20, 30);
      }
    }}/>
  }

export default TimeLineAppCanvas

CSS代码

#TimeLineApp{
    width:75%;
    height: 50%;
    /* width: 400px;
    height: 500px; */
    background: #FFFFFF;
    margin: 0 auto;
    display: block;
    margin-top: 2em;
    margin-bottom: 2em;
    cursor: pointer;
    
}

#TimeLineApp canvas {
    width: 100%;
    height: 100%;
  }

TimeLineApp容器代码

import './TimeLineApp.css'
import TimeLineAppCanvas from './Files/TimeLineAppCanvas';

function TimeLineApp() {
    return(
    <div>
        <TimeLineAppCanvas id='TimeLineApp' ></TimeLineAppCanvas>
    </div>
    )
}

export default TimeLineApp

问题根源

  1. Canvas尺寸不匹配:Canvas有两个关键尺寸——CSS显示尺寸和内部绘图缓冲区尺寸。你仅通过CSS将Canvas设为width:100%; height:100%;,但未设置Canvas的width和height属性,导致绘图缓冲区默认是300x150像素。浏览器会将这个小缓冲区拉伸到CSS指定的容器尺寸,最终造成坐标偏移。
  2. React状态异步更新问题:点击事件中调用handleCoords(e)更新coords状态后,立刻使用coords.x和coords.y绘制矩形,但React状态更新是异步的,此时拿到的仍是旧的坐标值,导致绘制位置错误。

解决方案

1. 同步Canvas显示尺寸与绘图缓冲区尺寸

修改TimeLineAppCanvas组件,添加逻辑确保Canvas的绘图缓冲区尺寸与显示尺寸一致,并在点击事件中直接计算正确坐标:

import React, {useRef, useEffect} from 'react'

const TimeLineAppCanvas = props => {
    const canvasRef = useRef(null);

    useEffect(() => {
        const canvas = canvasRef.current;
        if (!canvas) return;
        
        // 获取容器实际尺寸,同步Canvas绘图缓冲区尺寸
        const updateCanvasSize = () => {
            const { width, height } = canvas.parentElement.getBoundingClientRect();
            canvas.width = width;
            canvas.height = height;
        };

        updateCanvasSize();
        // 监听窗口resize,确保尺寸变化时同步更新
        window.addEventListener('resize', updateCanvasSize);
        return () => window.removeEventListener('resize', updateCanvasSize);
    }, []);

    const handleCanvasClick = (e) => {
        const canvas = canvasRef.current;
        if (!canvas) return;
        
        const rect = canvas.getBoundingClientRect();
        // 计算相对于绘图缓冲区的真实坐标
        const scaleX = canvas.width / rect.width;
        const scaleY = canvas.height / rect.height;
        const x = (e.clientX - rect.left) * scaleX;
        const y = (e.clientY - rect.top) * scaleY;

        const ctx = canvas.getContext("2d");
        ctx.strokeStyle = "#0095DD";
        ctx.strokeRect(x, y, 20, 30);
    };

    return <canvas ref={canvasRef} {...props} onClick={handleCanvasClick}/>
}

export default TimeLineAppCanvas

2. 移除冗余的MousePosition组件

不再依赖状态存储坐标,直接在点击事件中计算,彻底避免异步状态更新带来的问题。

3. 优化CSS容器尺寸

给容器设置明确的最小高度,避免高度塌陷:

#TimeLineApp{
    width:75%;
    min-height: 50vh; /* 使用视口高度确保容器有明确高度 */
    background: #FFFFFF;
    margin: 0 auto;
    display: block;
    margin-top: 2em;
    margin-bottom: 2em;
    cursor: pointer;
}

#TimeLineApp canvas {
    width: 100%;
    height: 100%;
    display: block; /* 消除Canvas默认的inline-block间距 */
}

关键说明

  • Canvas的width/height属性是绘图的像素基础,CSS的width/height仅控制显示效果,两者必须匹配才能保证坐标映射正确。
  • 直接在事件回调中计算坐标,跳过React状态更新环节,避免异步延迟导致的位置错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 07:57:35