본문 바로가기

개발에 도움이 되는/CSS

Canvas (React version)

 

- Canvas : 테두리(border)도 컨텐츠(content)도 없는 단순한 흰 도화지 같은 사각형의 공간으로 다양한 그래픽 요소를 만들 수 있다. 간단한 도형을 그리는 것은 물론 데이터 시각화, 애니메이션, 웹 게임 등을 만들 수 있다.

 

 

- 기본 Step

 1. <canvas> tag 생성

<canvas ref={canvanRef} width={720} height={900} />
const canvasRef = useRef(null);

useEffect(() => {
    canvasRef.current.width = window.innerWidth;
    canvasRef.current.height = window.innerHeight;
  }, []);
  
  
<canvas ref={canvanRef} />

 

canvas의 크기는 반드시 태그 속성으로 해주거나 useRef를 통한 DOM 접근으로 설정해준다.

CSS 사용시 화면 왜곡 및 깨짐이 발생할 수 있다.

 

위 사진의 왼쪽은 문제가 없지만 CSS(styled-components)로 처리하니 깨짐이 발생하였다.

 

 

 2. tag와 useRef 연결

const canvasRef = useRef(null);

<canvas ref={canvasRef} width={720} height={900}/>

 

 3. Context 가져오기

canvas 객체를 가져오기 위한 과정으로 getContext로 2d모드의 canvas 객체를 가져온다.

 

  const canvasRef = useRef(null);
  const [ctx, setCtx] = useState();

  useEffect(() => {
    const canvas = canvasRef.current;
    setCtx(canvas?.getContext("2d"));
    
  }, []);
  
<canvas ref={canvasRef} width={720} height={900}/>

 

 4. 가져온 canvas 객체(ctx)에 도형 그리기

 ex) Text가 들어간 사각형 그리기

ctx.fillStyle = "black"; // 글자 색 설정
ctx.font = "normal bold 20px sans-serif"; // font 설정
ctx.fillText("표시할 텍스트", x좌표, y좌표); // 글자 표시


ctx.fillStyle = "blue"; // 내부 색 설정
ctx.fillRect(시작 x좌표, 시작 y좌표, width 크기, height 크기); // 색 채워진 사각형 그리기


ctx.strokeStyle = "red"; // 선 색 설정
ctx.lineWidth = 0.5; // 선 굵기
ctx.strokeRect(시작 x좌표, 시작 y좌표, width 크기, height 크기); // 색 채우기 없는 사각형 그리기


// 영역 지우기
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // canvas 전체 영역

도형을 그리기 위한 여러 메소드가 존재한다. 아래 사이트에서 직접 코딩해보면서 확인 가능하다.

(참고 사이트 http://tcpschool.com/html/html5_graphic_canvas )

 

 

- 마우스 좌표 구하기

보통은 아래와 같이 구한다.

브라우저 화면상 마우스 x 좌표 : event.clientX

브라우저 화면상 마우스 y 좌표 : event.clientY

브라우저 화면상 캔버스 위치 x 좌표 : ctx.canvas.offsetLeft

브라우저 화면상 캔버스 위치 y 좌표 : ctx.canvas.offsetTop

canvas 좌표 = ( event.clientX - ctx.canvas.offsetLeft, event.clientY - ctx.canvas.offsetTop )

 

하지만 canvas positon을 absolute로 바꾸어 위치를 옮기게 된다면 위와 같이 좌표를 구한다면 엉뚱한 곳을 그리게 된다.

그래서 ViewPort의 상대적인 위치를 알려주는 getBoundingClientRect()를 이용하여 구하였다.

 

 

getBoundingClientRect()를 이용하여 구한 canvas 좌표 

canvas 좌표 = ( event.clientX - canvasRef.current.getBoundingClientRect().left, event.clientY - canvasRef.current.getBoundingClientRect().top)

해당 좌표를 이용하면 ViewPort에 따라 상대 좌표를 구할 수 있다.

 

 

- Mouse Event 이용하기

onMouseDown (마우스 버튼 누를 때), onMouseMove (마우스 커서 이동 중일 때), onMouseUp (마우스 버튼을 뗄 때), onMouseOut (마우스 커서가 영역을 벗어났을 때)을 이용

 

ex) 특정 화면에서 Mouse Drag로 사각형 그리기

import React, { useEffect, useRef, useState } from "react";
import styled from "styled-components";

const BaseCanvas = styled.canvas`
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: no-repeat center/100% url("/assets/sampleImage.jpg"); // canvas에 배경화면 설정
`;

const DragCanvas = styled.canvas`
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
`;

const DragConvas = () => {
  const canvasRef = useRef(null);
  const baseCanvasRef = useRef(null);
  const [ctx, setCtx] = useState();
  const [baseCtx, setBaseCtx] = useState();
  const [draw, setDraw] = useState(false);

  const [startX, setStartX] = useState();
  const [startY, setStartY] = useState();
  const [endX, setEndX] = useState();
  const [endY, setEndY] = useState();

  const [posX, setPosX] = useState();
  const [posY, setPosY] = useState();

  useEffect(() => {
    const canvas = canvasRef.current;
    const baseCanvas = baseCanvasRef.current;
    setCtx(canvas?.getContext("2d"));
    setBaseCtx(baseCanvas?.getContext("2d"));

    setPosX(canvasRef.current.getBoundingClientRect().left);
    setPosY(canvasRef.current.getBoundingClientRect().top);

    if (baseCtx) {
      baseCtx.clearRect(0, 0, baseCtx.canvas.width, baseCtx.canvas.height);
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    }
}, [ctx, baseCtx, draw]);

// start 좌표 저장 및 Draw 모드로 전환
  const drawStart = (e) => {
    setDraw(true);
    setStartX(e.clientX - posX);
    setStartY(e.clientY - posY);
  };

// draw 모드일 때 실시간으로 end 좌표 저장 및 이전 사각형을 지우기 위한 전체 삭제 및 사각형 그리기
  const drawMove = (e) => {
    if (draw) {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

      setEndX(e.clientX - posX);
      setEndY(e.clientY - posY);

      ctx.strokeStyle = "red";
      ctx.lineWidth = 0.5;
      ctx.strokeRect(startX, startY, endX - startX, endY - startY);

      ctx.fillStyle = "rgba(200, 0, 100, 0.2)";
      ctx.fillRect(startX, startY, endX - startX, endY - startY);
    }
  };

// draw 종료 시 모드 false로 바꾸고 좌표 초기화 
  const drawEnd = () => {
    setDraw(false);
    setStartX();
    setStartY();
    setEndX();
    setEndY();
  };

// canvas 영역 벗어날 시 모드 false로 바꾸고 좌표 초기화
  const drawOut = () => {
    if (draw) {
      setDraw(false);
      setStartX();
      setStartY();
      setEndX();
      setEndY();
    }
  };

  return (
    <div>
      <DragCanvas
        ref={canvasRef}
        width={720}
        height={900}
        onMouseDown={(e) => drawStart(e)}
        onMouseMove={(e) => drawMove(e)}
        onMouseUp={drawEnd}
        onMouseOut={drawOut}
      />
      <BaseCanvas ref={baseCanvasRef} width={720} height={900}/>
    </div>
  );
};

export default DragConvas;

canvas를 두 개를 사용한 이유는 mouseMove를 하는 동안 drag 하면서 사각형이 실시간으로 변화해야 되는데 그 방법으로 canvas 전체를 한번 clear 하고 새로 사각형을 그리는 방법을 선택했다. 그러나 하나만 썼을 때 clearRect 시 뒷 배경까지 지워졌기 때문에 하나는 배경용으로 사용하였다.

 

반응형

'개발에 도움이 되는 > CSS' 카테고리의 다른 글

박스 모델(Box Model)  (0) 2022.04.06
Flex  (0) 2022.02.11
가상 요소(Pseudo Element)  (0) 2022.02.10