Daily Pogle

[Javascript] Canvas API - 그림판 구현하기 본문

Programming language/Javasript

[Javascript] Canvas API - 그림판 구현하기

pogles 2023. 1. 10. 23:29

그림판에 필요한 기능에 대해서 하나하나씩 살펴보면서 코드를 구현한다.

 

그전에 이전 포스팅에서 간단하게 canvas 를 만들어서 그림판을 설정한다.

 

body 태그 안에 canvas 태그를 설정한다, canvas 태그를 통해서 그림 등 그리는게 가능해진다

 

[HTML]

<!DOCTYPE html>
<html lang="Ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Painting app</title>
</head>
<body>
	<canvas></canvas> <!-- canvas 생성 -->
</body>
</html>

 

[CSS]

canvas 의 크기와 테두리를 설정해준다. 또한 그림판은 흰 바탕으로 설정해준다.

canvas {
    width: 800px;
    height: 800px;
    border: 3px solid black;
    background-color: white;
}

 

[Javascript]

- 그림판은 2D 이므로 2d 속성을 사용하기 위해서 getContext("2d") 를 사용한다

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

 

 


기능 1.  그리기

 

 

1. 마우스 커서를 누르고 있으면 선을 그리고 마우스를 놓으면 선을 그리지 않는다.

 

 

마우스를 누르고 있는 상태냐, 놓은 상태냐에 따라 그리기 기능이 달라진다.

 

마우스를 누른 상태에서는 그리기 기능이 true 이고  놓은 상태면 그리기 기능이 false 라고 정해놓으면 편하다.

 

let isPainting = false; // 초기 상태는 그리지 않으므로 false

 

let isPainting = true / false

 

이벤트에 따라 isPainting 이 달라지므로 let 을 사용한다

 

 

마우스를 누르고 있는 상태는 isPainting 은 true

 

마우스를 뗀 상태는 isPainting 은 false 로 설정해준다

 

function startPainting() {
    isPainting = true;
}
function cancelPainting() {
    isPainting = false;
    ctx.beginPath(); // 밑에 설명
}

canvas.addEventListener("mousedown", startPainting);
canvas.addEventListener("mouseup", cancelPainting);

 

beginPath

더보기

< 왜 beginPath 를 사용하는가?? >

 

ctx 은 하나로 계속 연결되있는 요소 이다. ctx 를 분리하지 않고 계속 사용한다면

1번째 그림과 2번째 그림이 분리되지 않고 계속 연결되어 그려진다. 

 

쉽게 예를 들면 우리가 A4 용지에 색연필로 그림을 그리는데, 

종이에 색연필을 떼지 않고 계속 그림을 그리는 것과 같다

 

따라서 각자의 분리된 그림을 그리려면 하나의 그림을 완성한 다음 종이에 색연필을 떼고

다시 새로운 위치에서 그림을 그리는 것이다.

 

새로운 위치에서 다시 그리기  위해 ctx.beginPath() 를 사용하는 것이다.


 

2.  움직인 경로를 따라 선이 그려진다. 

 

 

canvas 에서 마우스가 움직이면, 선을 그리는 위치도 계속 마우스 위치로 움직이면서 그려져야한다.

 

function onMove(event) {
	// mousedown 시 startingPaint 함수가 호출되어 isPainting 값이 true 가 됨.
	if (isPainting) {
    	ctx.lineTo(event.offsetX, event.offsetY);
        ctx.stroke();
        return;
    }
    ctx.moveTo(event.offsetX, event.offsetY);
}

canvas.addEventListener("mousemove",onMove); // canvas 영역에서 마우스를 움직이면 onMove 함수 호출
ctx.lineTo()  이전 ctx 위치에서 현재 위치까지 선을 긋는다. ctx.stroke() 를 통해 선을 그려준다(색채우기)
ctx.moveTo() 그리는 상태나 아닌 상태나 ctx 의 위치는 현재 위치로 계속 바뀌어야한다.

 

이벤트객체

더보기

[ event.offsetX / event.offsetY ]

이벤트 객체

 

function 을 선언하다보면 function fn(event) 을 표시해줄 떄가 있다

event 는 대표적인 표현이고 event 이외의 이름으로 써주어도 상관없다.

 

아무튼 function 애서의 event 는 이벤트 객체로, 현재 이벤트가 발생한 객체를 가르킨다.

 

짧게 이야기 한다면 event는 현재 일어난 이벤트의 정보를 알려준다.

 

event.offsetX 는 현재 발생한 이벤트의 X 위치를 알려주고

event.offsetY 는 현재 발생한 이벤트의 Y 위치 알려준다.

 


 

3. 그림판 범위를 벗어나면 그리기를 중단한다.

 

범위를 벗어나면 그리기 기능을 자동으로 중지해야한다.

 

function cancelPainting() { // 단 마우스가 canvas 밖으로 나가면 적용안됨.
    isPainting = false;
    ctx.beginPath(); // new path
}

canvas.addEventListener("mouseleave",cancelPainting);

기능 2. 선 굵기 설정하기.

 

[HTML]

<input id="line-width" type="range" value="10" min="1" max="20" step="0.5">

<input type="range"/>

[JS]

const lineWidth = document.getElementById("line-width");

function onLineWidthChange(event) {
    ctx.lineWidth = event.target.value;
}

lineWidth.addEventListener("change", onLineWidthChange);

 

 

- 현재 이벤트 객체(event) 의 범위값(target.value) 을 ctx 의 선굵기로 설정한다.

- input 바가 변경될 때마다 선굵기가 달라지는 기능을 한다.


기능 3. 색상선택하기

 

 

[HTML]

<!DOCTYPE html>
<html lang="Ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>MEME Maker</title>
</head>
<body>
    <canvas></canvas>
    <div id="color-bar">
        <!-- HTML 에 data- 를 사용하면 요소의 dataset 에 정보를 추가할 수 있음. -->
        <div class="color-option" style="background-color:#1abc9c;" data-color="#1abc9c"></div>
        <div class="color-option" style="background-color:#3498db;" data-color="#3498db"></div>
        <div class="color-option" style="background-color:#34495e;" data-color="#34495e"></div>
        <div class="color-option" style="background-color:#27ae60;" data-color="#27ae60"></div>
        <div class="color-option" style="background-color:#8e44ad;" data-color="#8e44ad"></div>
        <div class="color-option" style="background-color:#f1c40f;" data-color="#f1c40f"></div>
        <div class="color-option" style="background-color:#e74c3c;" data-color="#e74c3c"></div>
        <div class="color-option" style="background-color:#95a5a6;" data-color="#95a5a6"></div>
        <div class="color-option" style="background-color:#d35400;" data-color="#d35400"></div>
        <div class="color-option" style="background-color:#bdc3c7;" data-color="#bdc3c7"></div>
        <div class="color-option" style="background-color:#2ecc71;" data-color="#2ecc71"></div>
        <div class="color-option" style="background-color:#e67e22;" data-color="#e67e22"></div>    
    </div>
    <div>
        <input id="color" type="color"/>
    </div>
</body>
</html>

 

[CSS]

- 색상들을 묶는 #color-bar 는 중앙정렬

- color-option 에 커서를 올려놓으면 마우스 커서 모양이 달라짐(cursor: pointer)

- color-option 에 커서를 올려놓으면 1.2 배 확대(hover)

- color-option 을 클릭하면 0.9배 축소(active)

#color-bar {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 5px;
    padding-top: 10px;
}

.color-option {
    width: 50px;
    height: 50px;
    border-radius: 100px;
    cursor: pointer;
}

.color-option:hover {
    transform: scale(1.2);
}

.color-option:active {
    transform: scale(0.9);
}

 

1. color-option 요소를 클릭하면 해당 색상으로 그릴 수 있게 한다.

color-option 요소들

const colorOptions = Array.from(document.getElementsByClassName("color-option")); // 배열이 아니라 HTMLCollection 이므로 배열로 저장

function onColorClick(event) { // 색상버튼 누르면 색상변경
    ctx.strokeStyle = event.target.dataset.color;
    ctx.fillStyle = event.target.dataset.color;
}

colorOptions.forEach(color => color.addEventListener("click",onColorClick));

 

- 색상을 나타내는 colorOption class 들은 여러개 이므로 getElementByClassName 을 사용하는데 반환은 배열이 아니라 HTMLCollection 객체로 저장된다. 따라서 Array.from() 으로 감싸주어 배열로 변환한다

 

- colorOptions.forEach 를 통해 각각의 class element 들에 이벤트를 설정해준다.

- 이벤트객체(event) 에 접근하기 위해 target 을 사용하고 HTML 에서 임의로 설정해준 data-color 에 접근하기 위해 dataset.color를 사용한다.

 

- HTML 에서 설정한 data-color 색상을 현재 ctx 색상으로 설정한다.(strokeStyle / fillStyle)

 


 

2. color-option 요소 외의 색상 선택하기(사용자 설정 색상 선택)

 

<input type="color" id="color">

const color = document.getElementById("color");

function onColorChange(event) { // input(type=color)의 색상값을 변경하면 바뀜.
    ctx.strokeStyle = event.target.value;
    ctx.fillStyle = event.target.value;
}

color.addEventListener("change",onColorChange);

 

- 현재 이벤트가 진행되고 있는 input type="color" 의 객체에 접근해서 색상값을 얻고, ctx 색상에 적용시킨다.

 

 

 


기능4. 모드변경(그리기, 칠하기) / 초기화 / 지우개  

 

 

<button id="destroy-btn">Destroy</button>
<button id="eraser-btn">Eraser</button>
<button id="mode-btn">Mode</button>

1. 모드변경(그리기, 칠하기)

 

선을 그리는 기능을 사용할때는 draw 버튼을 누르고 색채우기를 기능을 사용할때는 fill 버튼을 눌러서 사용한다.

 

선 그리기 때와 마찬가지로 let isFilling = false / true을 통해 조건에 따라 활성화시키거나 비활성화 시켜야한다.

 

버튼을 눌러 Fill(칠하기) 모드로 전환시켜주고 canvas 아무 곳이나 클릭해주면 색칠하기가 된다.

 

const modeBtn = document.getElementById("mode-btn");
let isFilling = false;

function onModeClick() { // 버튼 누르면 fill / draw 모드 전환
    if (isFilling) {
        isFilling = false;
        modeBtn.innerText = "Fill"
    } else {
        isFilling = true;
        modeBtn.innerText = "Draw"
    }
}

function onCanvasClick() { // 캔버스 전체크기만큼 칠해주기
    if (isFilling) {
        ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);  
    }
}

canvas.addEventListener("click", onCanvasClick);
modeBtn.addEventListener("click",onModeClick);

 

2. 초기화

 

 

초기화는 단순히 canvas 전체를 하얀바탕의 색상으로 전체를 칠하는 것이다.

 

 
const destroyBtn = document.getElementById("destroy-btn");

function onDestroyClick() { //  초기화 버튼 누르면 백지상태로 변경
    ctx.fillStyle = "white";
    ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); 
}

destroyBtn.addEventListener("click",onDestroyClick);

 

- 초기화버튼(destroy)를 누르면 ctx 를 통해 (0,0) 부터 (canvas 너비, canvas 높이) 까지에 달하는 canvas 전체 범위를 하얀색으로 칠한다.

 


 

3. 지우개

 

 

지우개는 흰색으로 그림을 그리는 것과 같다. 따라서 그림색상을 하얀색으로 설정해주면 된다.

 

const eraserBtn = document.getElementById("eraser-btn");

function onEraserClick() { // 지우개는 흰색으로 그려주는 것과 같음.
    ctx.strokeStyle = "white";
    isFilling = false;
    modeBtn.innerText = "Fill";
}

eraserBtn.addEventListener("click",onEraserClick);