ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [HTML] canvas 캔버스 도형 그리기 (사각형, 원)
    HTML 2023. 3. 25. 17:04

    html canvas를 이용하여 드로잉 애플리케이션을 구현하던 중 도형 그리기 기능을 추가하고 싶었다.

    (도형 중 사각형을 위주로 설명한다.)

     

    canvas에서 사각형을 그리는 방법은 다음과 같다.

    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.fillRect(x, y, width, height); // 채워진 사각형
    ctx.strokeRect(x, y, width, height); // 윤곽선만 있는 사각형

     

    하지만 우리가 원하는 건 캔버스 안에서 드래그를 통해 도형을 그리는 것이다.

    이를 위해 캔버스에 이벤트로 mousedown, mousemove, mouseup 을 사용한다.

    mousedown을 통해 그리기 시작을 알리고 시작된 위치를 저장한다.

    mousemove를 통해 현재 위치를 가지고 사각형을 그린다.

    mouseup을 통해 그리기 끝을 알린다.

    간략하게 코드로 표현하면 다음과 같다.

    let isDraw = false;
    let startX, startY, currX, currY;
    
    canvas.onmousedown = (e) => {
    	isDraw = true;
    	startX = e.clientX - e.offsetX;
    	startY = e.clientY - e.offsetY;
    }
    
    canvas.onmousemove = (e) => {
    	if(!isDraw) return
    	currX = e.clientX - e.offsetX;
    	currY = e.clientY - e.offsetY;
    	ctx.strokeRect(startX, startY, currX - startX, currY - startY);
    }
    
    canvas.onmouseup = () => {
    	isDraw = falsa;
    }

     

     

    하지만 mousemove 이벤트는 이동 중 여러번 실행되기 때문에 결과적으로 사각형을 드래그를 했을 때 여러 번의 사각형이 그려진다. 이를 해결하기 위해 mousemove 시작할 때 캔버스를 지우게 되면 원하는 대로 사각형이 그려지게 된다.

     

    canvas.onmousemove = (e) => {
    	if(!isDraw) return
    	ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    	currX = e.clientX - e.offsetX;
    	currY = e.clientY - e.offsetY;
    	ctx.strokeRect(startX, startY, currX - startX, currY - startY);
    }

     

    하지만 사각형을 그리게 되면 이전에 내가 캔버스에 그려둔 그림까지도 모두 지워지게 된다는 뜻이다. 전에 그렸던 그림을 유지하면서 사각형을 추가하려면 어떻게 할지 고민이 되었다.

     

     

    이를 해결하기 위해 생각한 방법은 도형을 그리는 임시 캔버스를 따로 두는 방법이었다.

     

    1. 기존 캔버스와 임시 캔버스의 위치를 겹쳐 놓는다. (기존 캔버스가 위로 가게)

    2. 사용자가 도형 툴을 선택하게 되면 css z-index를 통해 도형 캔버스를 위로 올린다.

    3. 사용자는 임시 캔버스에 도형을 그린다.

    4. 이때 mouseup 이벤트가 발생하면 임시 캔버스의 이미지를 얻어와 기존 캔버스에 그린다.

    5. clearRect를 통해 임시 캔버스를 지운다.

     

     

    1~2.

    임시 캔버스와 기존 캔버스를 display: absolute를 통해 겹쳐놓는다. 만약 사용자가 도형 툴을 선택하면 불리언 상태 isShapeTool가 true가 되면서 기존 캔버스보다 위로 올라가게 되면서 임시 캔버스에 그릴 수 있는 상태가 된다.
    (아래는 react, tailwind css 문법으로 간결하게 작성하였다.)

    <div className="relative">
     <canvas className={`absolute ${isShapeTool ? "z-10" : ""}`} /> //임시 캔버스
     <canvas className="absolute" /> // 기존 캔버스
    </div>

     

    3~5

    이후 임시 캔버스에서 도형 그리는 코드를 작성 후 mouseup 이벤트에서 toDataURL()을 이용하여 해당 캔버스 이미지를 얻을 수 있다. 참고로 리턴값은 base64 문자열 형태이다. 기본으로 png로 받을 수 있기 때문에 그린 영역 제외는 투명하다. Image() 생성자로 이미지 인스턴스를 만든 후 src에 할당한다. 그리고 기존 캔버스에 drawImage()를 이용하여  붙여넣기 하면 된다. 이후 clearRect()를 통해 임시 캔버스를 지우면 우리가 원하는 기능이 구현 가능하다.

    canvasTemp.onmouseup = () => {
          isDraw = false;
          const canvasImg = canvasTemp.toDataURL();
          const img = new Image();
          img.src = canvasImg;
          img.onload = () => {
            ctx.drawImage(img, 0, 0);
            ctxTemp.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
          };
    };

     

     

    + 원 그리는 법

     

    const ctx = canvas.getContext("2d");
    ctx.beginPath();
    ctx.arc(x, y, radius, startAngle, endAngle);
    ctx.stroke(); // or ctx.fill();

     

     

    마우스 드래그로 원을 그리려면 아래와 같이 작성하면 된다.

    const radius = Math.sqrt(
    	Math.pow(startX - currX, 2) +
    	Math.pow(startY - currY, 2)
    );
    ctx.arc(startX, startY, radius, 0, 2 * Math.PI);

     

     

     

    타원이 아닌 원을 그려야하기 때문에 pow(제곱)를 이용하여 x,y 각각의 지름을 구한 후 더한다.

    이후 sqrt(제곱근)을 이용하여 그 중간 길이의 반지름을 구할 수 있다.

    원을 만들려면 시작각도인 0도에서 종료각도인 360도를 만들어야 한다 360도는 2π이니 2 * Math.PI로 작성한다.

     

     

     

     

     

     

     

     

     

     

    반응형

    'HTML' 카테고리의 다른 글

    캐노니컬 태그란?  (0) 2023.08.07
    [HTML] canvas 모바일에서 그리기  (0) 2023.04.11
    [HTML] 시맨틱 마크업  (0) 2022.08.19
    [HTML] <dialog> 태그  (0) 2022.08.04
    [HTML] form 태그와 웹접근성  (0) 2022.07.07

    댓글

Designed by Tistory.