🖥️Frontend/React

[React] canvas & x, y좌표 값으로 길 그리기

뉴발자 2023. 11. 28.
728x90

 

 

 

 

 

 

 

 

 

 

 

 

 

 

그림 1-1. Canvas API

 

 

Canvas API

자바스크립트와 HTML의 canvas 엘리먼트를 통해 그래픽을 그릴 수 있게 제공하는 API로서, HTML5의 구성요소이다.

 

주로 2D 그래픽을 다루는데 사용되고 있다.

 

비트맵 데이터의 픽셀 하나 하나를 조작할 수 있고 그래픽 처리 성능이 좋은 편이다.

 

 

Method

자주 사용되는 canvas의 메소드는 아래와 같다.

 메소드  기능
 beginPath()  새로운 경로(path)를 생성한다.
 새 경로를 생성하는 메소드이기 때문에 최초에 호출하지 않아도 된다.
 closePath()  경로를 닫는다.
 마지막 경로에 있는 점과 시작점을 연결한다.
 stroke()  경로에 선을 그려준다.
 fill()  경로의 내부를 채운다.
 moveTo()  아무것도 그리지 않고 시작 위치를 옮긴다.
 lineTo()  현재 위치에서 특정 위치까지 선을 그린다.
 fillRect()  시작 좌표와 width, height 값을 이용해 사각형을 그린다.
 fillStyle  fillRect 함수로 그려지는 사각형 안을 채워줄 색상을 지정한다. 
 clearRect()  시작 좌표와 width, height 값을 이용해 그려진 canvas를 지운다.
 strokeStyle  선의 색상을 설정한다.
 lineCap  선의 끝 부분의 스타일을 지정한다. ( butt, round, square )
 lineJoin  선이 꺾이는 부분의 스타일을 지정한다. ( bevel, round, miter)
 lineWidth  선의 굵기를 설정한다.

 

 

코드

interface & 임시 좌표 생성

interface PointProps {
  x: number;
  y: number;
};

const point: PointProps[] = [
  {
    x: 82,
    y: 65,
  },
  {
    x: 105,
    y: 65,
  },
  {
    x: 105,
    y: 97,
  },
  {
    x: 221,
    y: 97,
  },
  {
    x: 221,
    y: 120,
  },
];

 

 

useRef & HTML 코드 작성

import { useRef } from "react";
import styled from "styled-components";

const width = 600;
const height = 600;

const Layout = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

const Canvas = styled.canvas`
  z-index: 1000;
`;

const ParkingImage = styled.img`
  position: absolute;
  width: ${width}px;
  height: ${height}px;
`;

const CanvasContainer = () => {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  return (
    <Layout>
      <Canvas ref={canvasRef} width={width} height={height} />
      <ParkingImage
        src={require("이미지경로")}
      />
    </Layout>
  );
};

export default CanvasContainer;

 

 

선 그리기 코드 작성

const canvasRef = useRef<HTMLCanvasElement>(null);
let z = 0;
let i = 0;

const drawLine = () => {
  const canvas = canvasRef.current;
  const ctx = canvas?.getContext("2d");

  if (ctx) {
    ctx.strokeStyle = "red";  // 선 색깔
    ctx.lineCap = "square";
    ctx.lineJoin = "round";	// 선 끄트머리(?)
    ctx.lineWidth = 3;		// 선 굵기

    if( i >= point.length-1 ) {
      i = 0;
      ctx.beginPath();

      setTimeout(() => {
        ctx.clearRect(0, 0, width, height);
        drawLine();
      }, 1000);
        
      return;
    }

    if( i === 0 ) {
      ctx.beginPath();
      ctx.moveTo(point[i].x, point[i].y);
    } 
      
    const interval = setInterval(() => {
      if( point[i+1].x > point[i].x ) 
        const maxX = point[i+1].x - point[i].x;
          
        if( z >= maxX ) {
          z = 0;
          i++;
          clearInterval(interval);
          drawLine();
        }

        ctx.lineTo(point[i].x + z, point[i].y);
        ctx.stroke();
        z += 1;
        
      } else if( point[i].x > point[i+1].x ) {
        const maxX = point[i].x - point[i+1].x;

        if( z >= maxX ) {
          z = 0;
          i++;
          clearInterval(interval);
          drawLine();
        }
          
        ctx.lineTo(point[i].x - z, point[i].y);
        ctx.stroke();
        z += 1;
        
      } else if( point[i+1].y > point[i].y ) {
        const maxY = point[i+1].y - point[i].y;

        ctx.lineTo(point[i].x, point[i].y + z);
        ctx.stroke();
          
        if( z >= maxY ) {
          z = 0;
          i++;
          clearInterval(interval);
          drawLine();
        }
        
        z += 1;
        
      } else if( point[i].y > point[i+1].y) {
        const maxY = point[i].y - point[i+1].y;

        ctx.lineTo(point[i].x, point[i].y - z);
        ctx.stroke();
          
        if( z >= maxY ) {
          z = 0;
          i++;
          clearInterval(interval);
          drawLine();
        }
          
        z += 1;
      }
    }, 10);
  }
};

useEffect(() => {
  drawLine();
}, []);

 

코드 설명을 간단하게 해보면,

 

1. beginPath()로 새로운 선을 그린다고 선언한 다음 moveTo로 point 배열의 index에 해당하는 x, y 좌표로 이동한다.

 

2. setInterval 함수를 사용해서 40ms마다 좌표를 2씩 증가시켜서 선이 움직이는 것처럼 애니메이션 효과를 준다.

 

3. 이동한 좌표와 다음 index의 x, y 좌표 값을 비교해서 이동 방향을 정한다.

 

4. point 배열 사이즈 -1 만큼 함수가 실행되면 1초 후 canvas를 클리어하고 다시 drawLine 함수를 호출한다.

728x90

 

 

전체 코드

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

const width = 600;
const height = 600;

const Layout = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;

const Canvas = styled.canvas`
  z-index: 1000;
`;

const Image = styled.img`
  position: absolute;
  width: ${width}px;
  height: ${height}px;
`;

interface PointProps {
  x: number;
  y: number;
};

const point: PointProps[] = [
  {
    x: 82,
    y: 65,
  },
  {
    x: 105,
    y: 65,
  },
  {
    x: 105,
    y: 97,
  },
  {
    x: 221,
    y: 97,
  },
  {
    x: 221,
    y: 120,
  },
];

const CanvasContainer = () => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  let z = 0;
  let i = 0;

  const drawLine = () => {
  const canvas = canvasRef.current;
  const ctx = canvas?.getContext("2d");

  if (ctx) {
    ctx.strokeStyle = "red";
    ctx.lineCap = "square";
    ctx.lineJoin = "round";
    ctx.lineWidth = 3;

    if( i >= point.length-1 ) {
      i = 0;
      ctx.beginPath();

      setTimeout(() => {
        ctx.clearRect(0, 0, width, height);
        drawLine();
      }, 1000);
        
      return;
    }

    if( i === 0 ) {
      ctx.beginPath();
      ctx.moveTo(point[i].x, point[i].y);
    } 
      
    const interval = setInterval(() => {
      if( point[i+1].x > point[i].x ) 
        const maxX = point[i+1].x - point[i].x;
        
        if( z >= maxX ) {
          z = 0;
          i++;
          clearInterval(interval);
          drawLine();
        }

          ctx.lineTo(point[i].x + z, point[i].y);
          ctx.stroke();
          z += 1;
        
        } else if( point[i].x > point[i+1].x ) {
          const maxX = point[i].x - point[i+1].x;

          if( z >= maxX ) {
            z = 0;
            i++;
            clearInterval(interval);
            drawLine();
          }
          
          ctx.lineTo(point[i].x - z, point[i].y);
          ctx.stroke();
          z += 1;
        
        } else if( point[i+1].y > point[i].y ) {
          const maxY = point[i+1].y - point[i].y;

          ctx.lineTo(point[i].x, point[i].y + z);
          ctx.stroke();
          
          if( z >= maxY ) {
            z = 0;
            i++;
            clearInterval(interval);
            drawLine();
          }
        
          z += 1;
        
        } else if( point[i].y > point[i+1].y) {
          const maxY = point[i].y - point[i+1].y;

          ctx.lineTo(point[i].x, point[i].y - z);
          ctx.stroke();
          
          if( z >= maxY ) {
            z = 0;
            i++;
            clearInterval(interval);
            drawLine();
          }
          
          z += 1;
        }
      }, 10);
    }
  };

  useEffect(() => {
    drawLine();
  }, []);

  return (
    <Layout>
      <Canvas ref={canvasRef} width={width} height={height} />
      <Image
        src={require("이미지경로")}
      />
    </Layout>
  );
};

export default CanvasContainer;

 

 

사용 라이브러리

canvas API(설치x), styled-components

 

 

동작 화면

그림 1-2. 길찾기.gif

 

 

참고 사이트

https://nookpi.tistory.com/133

 

Canvas API

사내 세미나에서 진행한 Canvas api 글인데 블로그에도 올려둡니다~ Canvas api란? Canvas API는 javascript와 Html canvas 엘리먼트를 통해 그래픽을 그릴 수 있는 수단을 제공하는 api로, html5의 구성요소입니

nookpi.tistory.com

 

https://developer.mozilla.org/ko/docs/Web/API/Canvas_API

 

Canvas API - Web API | MDN

Canvas API는 JavaScript와 HTML <canvas> 엘리먼트를 통해 그래픽을 그리기위한 수단을 제공합니다. 무엇보다도 애니메이션, 게임 그래픽, 데이터 시각화, 사진 조작 및 실시간 비디오 처리를 위해 사용

developer.mozilla.org

 

 

 

 

 

 

 

 

 

 

728x90

댓글