React canvas 드래그로 사각형 그리기

penguin·2022년 3월 5일
1

canvas를 이용하여 드래그로 사각형 그리는 component를 구현하는 방법과 구현하며 발생한 문제를 해결한 방법을 알려주고자 한다.


우선 canvas를 이용하기 위한 기본적인 설정을 한다.

function DragCanvas() {
  const canvasRef = useRef(null);
  const [ctx, setCtx] = useState();
  
  useEffect(() => {
    const canvas = canvasRef.current;
    setCtx(canvas.getContext("2d");
  }, []);
  
  return (
    <canvas ref={canvasRef} width={250} height={250} />
  )
}

useRef를 이용해 canvas 엘리먼트에 접근한다.
canvasRefcanvas 요소의 ref 속성에 넣어주고 canvasRef.current를 이용해 접근 가능하다.
그리고 canvasRef.current.getContext("2d")를 이용해 CanvasRenderingContext2D를 반환받아 ctx state에 할당해주면 react에서 canvas를 이용하기 위한 기본적인 준비가 끝난다.


사각형 상자를 그리기 위해선 canvas에서 제공하는 ctx.strokeRect(x, y, width, height) 메소드를 이용한다.
x, y는 시작지점의 좌표이고 width, height는 상자의 넓이와 높이를 지정한다.
ctx.strokeStyle를 설정하여 사각형의 테두리 색깔을 지정한다.

사각형을 그리는 함수를 만든다.

function drawSquare() {
  ctx.strokeStyle = "red";
  ctx.strokeRec(0, 0, 50, 50);
}

이 함수를 canvas의 onmousedown이벤트에 이벤트핸들러로 넣어준다.

<canvas 
  ref={canvasRef} 
  width={500} 
  height={500}
  onMouseDown={drawSquare}
/>

이제 canvas를 클릭하면 0, 0 좌표에 높이, 너비 50의 빨간테두리 상자가 그려진다.


상자를 그리는 함수에서 event 객체를 파라미터로 받아 클릭한 위치에 상자가 그려지게 할 수 있다.

function drawSquare(e) {
  ctx.strokeStyle = "red";
  let x = e.clientX - canvasRef.current.offsetLeft;
  let y = e.clientY - canvasRef.current.offsetTop;
  let w = 50;
  let h = 50;
  ctx.strokeRect(x - w / 2, y - h / 2, w, h);
}

위의 함수를 이용하면 클릭한 부분을 중심으로 사각형을 그려준다.


드래그를 이용해 상자를 그리기 위해선 onMouseDown에서 클릭한 좌표를 저장하고 onMouseMove에서 상자를 그려줘야 하고 onMouseUp에서 그리는걸 중단해줘야한다.

const [pos, setPos] = useState([]);
const [isDraw, setIsDraw] = useState(false);

function drawStart(e) {
  setIsDraw(true);
  setPos([e.clientX - canvasRef.current.offsetLeft, e.clientY - canvasRef.current.offsetTop]);
}

function drawSquare(e) {
  if (!isDraw) return;
  ctx.strokeStyle = "red";
  let currentX = e.clientX - canvasRef.current.offsetLeft;
  let currentY = e.clientY - canvasRef.current.offsetTop
  ctx.strokeRect(pos[0], pos[1], currentX - pos[0], currentY - pos[1]);
}

function drawEnd() {
  setIsDraw(false);
}

<canvas 
  ref={canvasRef} 
  width={250} 
  height={250} 
  onMouseDown={drawStart} 
  onMouseMove={drawSquare} 
  onMouseUp={drawEnd}
/>

하지만 이런 방식으로 사각형을 그리면 드래그 중 좌표가 변할 때마다 사각형을 그리게 된다.


이 문제를 어떻게 해결할지 고민하다가 mousemove 이벤트가 호출될 때마다 우선 canvas를 초기화하는 방식을 떠올렸다.
canvas를 초기화하는 건 ctx.clearRect(x, y, width, height) 메소드를 이용한다. x,y 좌표에서 width, height만큼의 canvas를 초기화해준다.

function drawSquare(e) {
  if (!isDraw) return;
    ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
  ctx.strokeStyle = "red";
  let currentX = e.clientX - canvasRef.current.offsetLeft;
  let currentY = e.clientY - canvasRef.current.offsetTop;
  ctx.strokeRect(pos[0], pos[1], currentX - pos[0], currentY - pos[1]);
}

이제 드래그하면 상자가 원하는 대로 그려진다.


이 글을 보고 도움을 받는 분이 있기를 바라며 혹시 더 좋은 방법을 알고 계신다면 공유해주시면 감사하겠습니다.

profile
힘내자

1개의 댓글

comment-user-thumbnail
2022년 11월 12일

저도 똑같은 기능을 구현하다가 마우스 드래그할 때마다 사각형이 그려져서 어떻게 해야 되나 한참 고민했는데 clearRect 메서드가 있었네요! 잘 봤습니다~

답글 달기