Search code examples
reactjscanvasuse-effectuse-state

React - How to get client coordinates on canvas properly? (drawing a straight line with mouse)


I got to make "drawing a line" works, but this line wouldn't start from where my mouse starts, rather it'd start from very beginning {x: 0, y:0}. How to draw a line starting from where my mouse clicks?

I already used e.nativeEvent.offsetX and e.nativeEvent.offsetY for getting client X and Y, and this is used for just drawing feature, but I'm stuck how I can utilize it for drawing line, or changing my draw a line code for making it work. Thank you in advanced!

const canvasRef = useRef(null);
  const ctxRef = useRef(null);
  const [isDrawing, setIsDrawing] = useState(false);
  const [lineWidth, setLineWidth] = useState(5);
  const [lineColor, setLineColor] = useState("black");
  
  let startPosition = {x: 0, y: 0};
  let lineCoordinates = {x: 0, y: 0}; 

  const getClientOffset = (e) => {
    const {pageX, pageY} = e.touches ? e.touches[0] : e;
    const x = pageX - canvasRef.offsetLeft;
    const y = pageY - canvasRef.offsetTop;

    return {
       x,
       y
    } 
  } 

  const drawLine = () => {
   ctxRef.current.beginPath();
   ctxRef.current.moveTo(startPosition.x, startPosition.y);
   ctxRef.current.lineTo(lineCoordinates.x, lineCoordinates.y);
   ctxRef.current.stroke();
}

  useLayoutEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    ctx.strokeStyle = lineColor;
    ctx.lineWidth = lineWidth;
    ctx.lineCap = "round";
    ctx.lineJoin = "round";
    ctxRef.current = ctx;
  }, [lineColor, lineWidth]);

  const handleMouseDown = (e) => {
    setIsDrawing(true);

    // const posX = e.nativeEvent.offsetX;
    // const posY = e.nativeEvent.offsetY;

    // ctxRef.current.beginPath();
    // ctxRef.current.moveTo(
    //   posX,
    //   posY
    // );
    startPosition = getClientOffset(e);
  }

  const handleMouseMove = (e) => {
    if (!isDrawing) {
      return;
    } 
    ctxRef.current.lineTo(
      e.nativeEvent.offsetX,
      e.nativeEvent.offsetY
    );
    ctxRef.current.stroke();

    lineCoordinates = getClientOffset(e);
    drawLine();
  }

  const handleMouseUp = () => {
    ctxRef.current.closePath();
    setIsDrawing(false);
  }

enter image description here


Solution

  • The issue is let startPosition and let lineCoordinates – on each render update React will reinitialize these variables with {x: 0, y:0}. In order to remember any value you must use useState().

    I tried it out in a demo https://codesandbox.io/s/crazy-breeze-xj26c?file=/src/App.js