Search code examples
htmlreactjscanvas

drawing circle starts at wrong position


I wrote a function which allows me to draw circles on a canvas. Everything works fine, but the start position of my canvas is not the same position as my mouse position.

const stopDrawingCircle = () => {
    setIsDrawing(false);
    console.log(currentImage);
}
//Setting start position
const startDrawingCircle = (event:any) => {
    currentImage.circle.x = event.clientX;
    currentImage.circle.y = event.clientY;
    currentImage.circle.radius = 0;
    setIsDrawing(true);
};
const drawCircle = function(event:any){
    if(!isDrawing)
      return;
    let canvas = canvasRef.current as HTMLCanvasElement | null; 
    let ctx = canvas?.getContext('2d')
    //seems to be the problem console log at start says the result of this is zero
    var currentX = currentImage.circle.x - event.clientX;
    var currentY = currentImage.circle.y - event.clientY;
    currentImage.circle.radius = Math.sqrt(currentX * currentX + currentY * currentY)
    if(canvas != null && ctx != null){
        ctx.beginPath();
        ctx.arc(currentImage.circle.x, currentImage.circle.y, currentImage.circle.radius, 0, Math.PI*2);
        ctx.fill();
    }
}
return(
  <div className="main">
    <div id="imageSection">
      <canvas id="canvas" 
        onMouseDown={startDrawingCircle}
        onMouseUp={stopDrawingCircle}
        onMouseMove={drawCircle}
        ref={canvasRef}> </canvas>       
    </div>
  </div>
)

The result of drawing a circle is like this:

example of fail

I started with my mouse in the center of the image and he draws it in the "lower right corner"


Solution

  • event.clientX and event.clientY are relative to the current screen, not element being clicked.

    MDN MouseEvent.clientX docs:

    clientX
    A number, defaulting to 0, that is the horizontal position of the mouse event on the client window of user's screen;

    Use event.offsetX and event.offsetY to get the mouse position relative to the event target.

    However, since you're using React, you need to use event.nativeEvent.offsetX and event.nativeEvent.offsetY because react doesn't copy the offset properties onto their event proxy object.


    Full code example:

    import {useState, useRef} from "react";
    
    const currentImage = {
      circle: {
        x: 0,
        y: 0,
        radius: 10,
      }
    };
    
    export default function App() {
      const [isDrawing, setIsDrawing] = useState(false);
      const canvasRef = useRef<HTMLCanvasElement>(null);
    
      const stopDrawingCircle = () => {
          setIsDrawing(false);
          // console.log(currentImage);
      }
      //Setting start position
      const startDrawingCircle = (event:any) => {
          currentImage.circle.x = event.nativeEvent.offsetX;
          currentImage.circle.y = event.nativeEvent.offsetY;
          currentImage.circle.radius = 0;
          setIsDrawing(true);
      };
      const drawCircle = function(event:any){
          if(!isDrawing)
            return;
          let canvas = canvasRef.current as HTMLCanvasElement | null; 
          let ctx = canvas?.getContext('2d')
          //seems to be the problem console log at start says the result of this is zero
          var currentX = currentImage.circle.x - event.nativeEvent.offsetX;
          var currentY = currentImage.circle.y - event.nativeEvent.offsetY;
    
          currentImage.circle.radius = Math.sqrt(currentX * currentX + currentY * currentY)
          if(canvas != null && ctx != null){
              ctx.beginPath();
              ctx.arc(currentImage.circle.x, currentImage.circle.y, currentImage.circle.radius, 0, Math.PI*2);
              ctx.fill();
          }
      }
      return(
        <div className="main">
          <div id="imageSection">
            <canvas id="canvas" 
              onMouseDown={startDrawingCircle}
              onMouseUp={stopDrawingCircle}
              onMouseMove={drawCircle}
              ref={canvasRef}> </canvas>       
          </div>
        </div>
      )
    }