본문 바로가기
dev/javascript

[javascript] 자바스크립트 및 캔버스를 이용한 그림판 만들기

by 최연탄 2022. 4. 26.
728x90
반응형

참고: https://dev.to/javascriptacademy/create-a-drawing-app-using-javascript-and-canvas-2an1

튜토리얼에서는 브라우저에서 간단히 그림을 그릴 있도록 하는 스크립트를 만들 것입니다. 이를 위해 기본 javascript canvas API 사용할 것입니다. 튜토리얼을 마치고 나면 canvas API javascript 이벤트 처리에 대한 대략적인 흐름을 알게 것입니다.

HTML markup

전체 앱을 section으로 래핑하고 class 이름을 container로 해서 시작하겠습니다. 이는 툴바와 드로잉 보드를 정렬하는데 사용합니다.

이 안쪽에 툴바를 위치시킬 div를 넣고 javascript 작업을 수월하게 하기위해 이 element의 id는 toolbar로 설정 합니다.

툴바 안에는 두개의 input 필드를 넣을 건데 하나는 색상 지정을 위해, 다른 하나는 선의 굵기를 지정하도록 할 것입니다. 색상 input은 선의 색을 지정하는 것 이므로 id를 stroke로 하고, 두번째 input에는 숫자 값을 받을 것이고 id는 lineWidth라고 지정합니다. 잊지말고 각각의 input에 해당하는 레이블을 달아줍니다. 마지막으로 id가 clear인 버튼을 추가할 것인데 이것은 드로잉 보드를 깨끗이 지우는 기능을 넣을 것입니다.

다음으로 우리 앱의 실질적인 그림을 그릴 부분인 드로잉 보드를 추가합니다. 이는 canvas element 이고 레이아웃을 잡기위해 div로 래핑합니다.

마지막으로 body 마지막 부분에 script 태그를 추가해야합니다.

<section class="container">
  <div id="toolbar">
    <label for="stroke">Stroke</label>
    <input id="stroke" name='stroke' type="color">
    <label for="lineWidth">Line Width</label>
    <input id="lineWidth" name='lineWidth' type="number" value="5">
    <button id="clear">Clear</button>
  </div>
  <div class="drawing-board">
    <canvas id="drawing-board"></canvas>
  </div>
</section>
<script src="./index.js"></script>

CSS 스타일 추가

먼저 브라우저 기본 스타일로 정의된 padding margin 제거하고 시작하겠습니다. 또한 body 높이는 100% 설정하고 overflow: hidden으로 스크롤바를 안보이게 만듭니다.

body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  color: white;
}

container 높이를 100% 설정하고 flexbox 사용하도록 설정 합니다.

.container {
  height: 100%;
  display: flex;
}

툴바도 flexbox 쓰지만 세로정렬을 하게 합니다. 툴바의 가로길이는 그냥 100px 잡고 다른 부분과 차이나 보이도록 배경을 검정으로 설정합니다. 툴바 항목에 기본적인 스타일링을 적용해줍니다.

#toolbar {
  display: flex;
  flex-direction: column;
  width: 100px;
  background-color: black;
}

#toolbar * {
  margin-bottom: 5px;
}

#toolbar input {
  width: 100%;
}

javascript 구현

먼저 툴바와 드로잉 보드(canvas) 참조할 변수를 지정합니다.

const canvas = document.getElementById('drawing-board');
const toolbar = document.getElementById('toolbar');

다음으로 canvas context 가져와야 하는데 context 사용해 canvas 그림을 그릴 것입니다. 이를 위해 canvas getContext 메소드를 호출해 context 얻고 그냥 평면에 그림그릴 것이기 때문에 매개변수로 2D 넘겨줍니다.

const ctx = canvas.getContext('2d');

다음 단계에서는 오프셋(canvas 왼쪽 좌표와 브라우저 화면의 왼쪽 사이의 거리) 가져올 것입니다. 예제의 경우 canvas 높이 100% 이기 때문에 top 오프셋은 0px 되고 left 오프셋은 화면상에 툴바 옆에 있기 때문에 100px 됩니다. 다음으로 canvas 가로, 세로 길이를 계산해 설정합니다. 계산 방법은 브라우저 화면의 가로, 세로 길이에서 canvas 오프셋을 빼면 얻을 있습니다.

const canvasOffsetX = canvas.offsetLeft;
const canvasOffsetY = canvas.offsetTop;

canvas.width = window.innerWidth - canvasOffsetX;
canvas.height = window.innerHeight - canvasOffsetY;

이제 전역 변수를 설정해야합니다. isPainting 변수는 현재 사용자가 그림을 그리고 있는 상태인지 아닌지에 대한 정보를 가지게 하고 기본 선의 굵기는 5px 지정합니다.

let isPainting = false;
let lineWidth = 5;

이제 이벤트 리스너를 추가할 시간입니다. 먼저 툴바에 클릭 이벤트 리스너를 추가해야합니다. 만약 이벤트의 event.target.id clear이면(사용자가 clear 버튼을 클릭한 경우) clearRect 매소드에 매개변수로 canvas 가로, 세로 길이를 넘겨서 canvas 깨끗이 지우도록 작성하겠습니다. 메소드의 기본적인 기능은 매개변수로 받은 가로, 세로 크기에 해당하는 canvas 모든 픽셀을 지우는 것입니다. 튜토리얼의 경우 canvas 모든 영역이 됩니다.

toolbar.addEventListener('click', (event) => {
  if (event.target.id === 'clear') {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }
});

다음으로 색상과 선굵기 input 값이 변할 때를 제어해야합니다. 여기서는 각각의 input 이벤트 리스너를 추가하는게 아닌 통합으로 작성해보겠습니다. 이를 위해 부모 element 이벤트 리스너를 달아 이벤트를 제어하도록 것입니다. 각각의 input 필드를 구분하기위해 event.target 체크하도록 하겠습니다. 만약 색상이 바뀐다면 canvas context strokeStyle 설정하고 선의 굵기가 바뀐다면 전역변수 lineWidth 값을 설정할 것입니다.

toolbar.addEventListener('change', (event) => {
  if (event.target.id === 'stroke') {
    ctx.strokeStyle = event.target.value;
  }

  if (event.target.id === 'lineWidth') {
    lineWidth = event.target.value;
  }
});

다음으로 드로잉 컨트롤을 구현하겠습니다. mousedown 이벤트가 발생하면(사용자가 마우스 버튼을 클릭 손가락을 떼지 않고 잡고있을 ) isPainting 변수를 true 설정하고 context beginPath() 메소드를 사용하여 선그리기 시작 설정을 하고 moveTo() 메소드로 선을 그릴 시작점 설정을 합니다. 상태에서 사용자가 마우스 버튼을 놓으면 isPainting false 바꿔줍니다.

canvas.addEventListener('mousedown', (event) => {
  isPainting = true;

  ctx.beginPath();
  ctx.moveTo(event.clientX - canvasOffsetX, event.clientY);
});

canvas.addEventListener('mouseup', (event) => {
  isPainting = false;
});

canvas 마우스 포인트가 이동할 이벤트를 제어하기 위해 mousemove 이벤트 리스너를 추가해줍니다. 여기서는 먼저 isPainting 상태인지 아닌지 판단먼저 합니다. 그리는 상태가 아닌데 canvas위의 포인트가 바뀐다면 간단히 return으로 그림그리기 처리를 안하도록 합니다. 이제 그리기 상태라면 선의 굵기를 먼저 설정합니다. 값은 전역변수에 저장되도록 해놨으므로 값을 사용하여 선의 굵기를 설정하고 lineCap 둥굴게 설정합니다. 다음 lineTo() 메소드를 이용해 그림을 그릴 현재 마우스 좌표를 입력합니다. 여기서 주의할 점은 왼쪽에 툴바가 있으므로 마우스 좌표에서 오프셋을 값을 캔버스에 그리도록 해야한다는 점입니다. 마지막으로 stroke() 메소드를 호출해 지정된 색과 굵기로 canvas 선을 그립니다.

canvas.addEventListener('mousemove', (event) => {
  if (!isPainting) {
    return;
  }

  ctx.lineWidth = lineWidth;
  ctx.lineCap = 'round';

  ctx.lineTo(event.clientX - canvasOffsetX, event.clientY);
  ctx.stroke();
});

이로서 간단한 그림판 앱이 완성됐습니다. 아래에 전체 코드를 첨부합니다.

반응형

index.html

<html>

<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="styles.css">
  <title>Drawing app</title>
</head>

<body>
  <section class="container">
    <div id="toolbar">
      <label for="stroke">Stroke</label>
      <input id="stroke" name='stroke' type="color">
      <label for="lineWidth">Line Width</label>
      <input id="lineWidth" name='lineWidth' type="number" value="5">
      <button id="clear">Clear</button>
    </div>
    <div class="drawing-board">
      <canvas id="drawing-board"></canvas>
    </div>
  </section>
  <script src="./index.js"></script>
</body>

</html>

styles.css

body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  color: white;
}

.container {
  height: 100%;
  display: flex;
}

#toolbar {
  display: flex;
  flex-direction: column;
  width: 100px;
  background-color: black;
}

#toolbar * {
  margin-bottom: 5px;
}

#toolbar input {
  width: 100%;
}

index.js

const canvas = document.getElementById('drawing-board');
const toolbar = document.getElementById('toolbar');
const ctx = canvas.getContext('2d');

const canvasOffsetX = canvas.offsetLeft;
const canvasOffsetY = canvas.offsetTop;

canvas.width = window.innerWidth - canvasOffsetX;
canvas.height = window.innerHeight - canvasOffsetY;

let isPainting = false;
let lineWidth = 5;

toolbar.addEventListener('click', (event) => {
  if (event.target.id === 'clear') {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }
});

toolbar.addEventListener('change', (event) => {
  if (event.target.id === 'stroke') {
    ctx.strokeStyle = event.target.value;
  }

  if (event.target.id === 'lineWidth') {
    lineWidth = event.target.value;
  }
});

canvas.addEventListener('mousedown', (event) => {
  isPainting = true;

  ctx.beginPath();
  ctx.moveTo(event.clientX - canvasOffsetX, event.clientY);
});

canvas.addEventListener('mouseup', (event) => {
  isPainting = false;
});

canvas.addEventListener('mousemove', (event) => {
  if (!isPainting) {
    return;
  }

  ctx.lineWidth = lineWidth;
  ctx.lineCap = 'round';

  ctx.lineTo(event.clientX - canvasOffsetX, event.clientY);
  ctx.stroke();
});

관련 글

자바스크립트 이벤트 핸들러 등록 (addEventListener)

반응형

댓글