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

React画布矩形绘制异常:悬停触发绘制,需实现点击绘制+移出消失

问题解决:仅鼠标点击拖拽时绘制矩形,移出范围自动消失

问题根源

  1. stopDrawingRectangle中注释了setIsDrawing(false),导致isDrawing状态始终为true,鼠标移动就会触发绘制
  2. 缺少鼠标移出矩形范围时的检测与画布清空逻辑
  3. 事件绑定范围与时机不够精准

修复后的完整代码

import { useState, useRef, useEffect } from 'react';

const CanvasDrawer = () => {
  const [isDrawing, setIsDrawing] = useState(false);

  const canvasRef = useRef(null);
  const videoRef = useRef(null);

  const startX = useRef(null);
  const startY = useRef(null);
  const canvasOffSetX = useRef(0); // 初始化偏移量
  const canvasOffSetY = useRef(0);
  const currentRect = useRef(null); // 存储当前绘制的矩形信息

  const contextRef = useRef(null);

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

    // 初始化canvas尺寸和偏移量
    canvas.width = videoRef.current.clientWidth;
    canvas.height = videoRef.current.clientHeight;
    const rect = canvas.getBoundingClientRect();
    canvasOffSetX.current = rect.left;
    canvasOffSetY.current = rect.top;
    contextRef.current = canvas.getContext("2d");

    const handleMouseMove = (event) => {
      if (isDrawing) {
        drawRectangle(event);
        // 检测鼠标是否移出当前矩形范围
        checkMouseOutOfRect(event);
      }
    };

    // 绑定事件到canvas,而非document,减少全局监听
    canvas.addEventListener("mousemove", handleMouseMove);

    return () => {
      canvas.removeEventListener("mousemove", handleMouseMove);
    };
  }, [isDrawing]);

  const startDrawingRectangle = ({ nativeEvent }) => {
    nativeEvent.preventDefault();
    nativeEvent.stopPropagation();

    // 计算相对canvas的起始坐标
    startX.current = nativeEvent.clientX - canvasOffSetX.current;
    startY.current = nativeEvent.clientY - canvasOffSetY.current;
    setIsDrawing(true);
  };

  const drawRectangle = ({ nativeEvent }) => {
    if (!isDrawing || !startX.current || !startY.current) return;

    const context = contextRef.current;
    const canvas = canvasRef.current;
    if (!context || !canvas) return;

    const newMouseX = nativeEvent.clientX - canvasOffSetX.current;
    const newMouseY = nativeEvent.clientY - canvasOffSetY.current;

    // 计算矩形的左上角和右下角坐标
    const xtl = Math.min(startX.current, newMouseX);
    const ytl = Math.min(startY.current, newMouseY);
    const xbr = Math.max(startX.current, newMouseX);
    const ybr = Math.max(startY.current, newMouseY);

    const rectWidth = xbr - xtl;
    const rectHeight = ybr - ytl;

    // 清空画布并绘制新矩形
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.strokeRect(xtl, ytl, rectWidth, rectHeight);

    // 存储当前矩形信息,用于后续移出检测
    currentRect.current = { xtl, ytl, xbr, ybr };
  };

  const checkMouseOutOfRect = ({ nativeEvent }) => {
    if (!currentRect.current) return;

    const mouseX = nativeEvent.clientX - canvasOffSetX.current;
    const mouseY = nativeEvent.clientY - canvasOffSetY.current;
    const { xtl, ytl, xbr, ybr } = currentRect.current;

    // 判断鼠标是否在矩形外
    if (mouseX < xtl || mouseX > xbr || mouseY < ytl || mouseY > ybr) {
      stopDrawingRectangle();
    }
  };

  const stopDrawingRectangle = () => {
    setIsDrawing(false);
    const canvas = canvasRef.current;
    const context = contextRef.current;
    if (canvas && context) {
      context.clearRect(0, 0, canvas.width, canvas.height);
    }
    // 重置所有临时变量
    startX.current = null;
    startY.current = null;
    currentRect.current = null;
  };

  return (
    <div style={{ position: 'relative' }}>
      <video ref={videoRef} style={{ width: '100%', height: 'auto' }} />
      <canvas
        ref={canvasRef}
        style={{ position: 'absolute', top: 0, left: 0, cursor: 'crosshair' }}
        onMouseDown={startDrawingRectangle}
        onMouseUp={stopDrawingRectangle}
        onMouseLeave={stopDrawingRectangle}
      />
    </div>
  );
};

export default CanvasDrawer;

关键修复点

  • 恢复setIsDrawing(false):在stopDrawingRectangle中设置状态为false,彻底停止绘制逻辑
  • 添加矩形移出检测:通过checkMouseOutOfRect函数判断鼠标位置,移出则清空画布并停止绘制
  • 优化事件绑定:将mousemove绑定到canvas而非document,减少不必要的全局事件触发
  • 初始化偏移量:在useEffect中设置canvas的偏移量,确保坐标计算准确
  • 临时变量重置:停止绘制时清空所有临时存储的坐标和矩形信息

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 08:35:17