[JavaScript] 그림판만들기(Camvas이용)
in JavaScript on Study
1️⃣ 목표
Canvas API를 이용해 그림판을 만들어볼 계획인데 기본 베이스는 노마드코더 강의를 보고 만들었습니다. (그렇기 때문에 해당내용은 작성하지 않았지만 노마드코더 사이트에서 무료로 배우실 수 있습니다.)
👉🏻👉🏻👉🏻바닐라 JS로 그림판 만들기 - 노마드코더(니콜라스)- 이렇게 만들어진 기본 그림판에 추가적인 기능을 구현할 예정입니다.
- 이전에 <p5.js>를 이용하여 “raycasting”을 구현하는 법을 배운적이 있습니다. <p5.js>는 Canvas API를 포함하고 있는
라이브러리 입니다. 여기서 Canvas의 강력한 기능을 경험한적이 있습니다. - 이처럼 Canvas API관련 라이브러리가 많기 때문에 강력하지만 다소 어려울 수 있는 Canvas사용을 쉽게 다룰 수 있게 해줍니다.
- 하지만 이번에는 라이브러리 사용없이 기본적인 Canvas를 이용할 것입니다.
🎨 Canvas API 설명사이트
🎨 p5.js 공식사이트
🍌 p5.js를 이용하여 raycasting을 구현한 내용의 포스트
2️⃣ 도구선택 유연하게 만들기
(1) 기존의 도구선택방식
- 기본적으로 구현한 그림판의 경우 도구가 팬과 채우기 두가지 였습니다.
let filling = false;
- 도구가
두가지 이기 때문에filling
변수가 false이면 팬으로 그리기, true이면 채우기동작을 하도록 구현해도 문제가 없었습니다.
(2) 도구를 세가지이상 늘어난다면?
- 만약 도구가 세가지 이상으로 늘어난다면,
filling
과 같이boolean 형으로 사용할 변수가 한개로는 부족해 집니다. - 만약 원을 그리는 도구를 추가한다고 하면 다음과 같이 독립적으로 참, 거짓을 판별하는 변수들을 선언할 수 있습니다.
let isPaint = false;
let isPen = false;
let isCircle = false;
- 하지만 화면에 그리기위해서는 모두
마우스를 클릭 하는 동작이벤트가 겹치기 때문에 사용하는 동구가 아니라면 모두false로 바꿔주어야합니다. - 이런식으로 처리하려면 따로 처리하는 함수를 구현해야하고 추후에 새로운 도구가 추가되면 수정하는데도 불편할 것같습니다. (유연하지 못한 코드)
(3) 최종 도구 선택방식
- 위에서 처럼 boolean형으로 각각의 도구들을 구분하여 사용하는 것은 여러모로
비효율</b>적인 것 같습니다. 대신에 다음과 같이 새로운 방식이 좋을 것같습니다.
const PEN = 1;
const PAINT = 2;
const CIRCLE = 3;
let tool = PEN;
- 위와 같이 매크로 형식으로 도구를 지정해놓고
tool
이라는 변수에 선택적으로 지정해서 사용하는 방식입니다. - 이제 도구를 설정해주는 함수도 따로 구현할 필요가 없어졌습니다.
if (paint) {
paint.addEventListener("click", () => (tool = PAINT));
}
if (pen) {
pen.addEventListener("click", () => (tool = PEN));
}
- 위와같이 각각의 도구선택 이벤트들도 깔끔하게 구현할 수 있으며 새로운 도구가 추가되어도 유연하게 추가하고 제거할 수 있을 것 같습니다.
3️⃣ 도구에 맞는 이벤트 맞춤설정하기
(1) 이벤트 중복문제
- 이제
tool
이라는 변수로 어떤 도구를 쓰는지 쉽게알 수 있습니다. - 하지만 각각의 도구들은 모두 마우스관련 이벤트이고 모두 같은 화면(종이)에서 동작합니다. 결국
"이벤트가 겹칠 수 밖에 없습니다.</b> - 물론 마우스 이벤트마다 각각의 도구들을
if, else
로 처리하여 동작을 다르게하도록 구현할 수 있습니다. 마우스 이벤트는 종류는 한정적이기 때문에 새로운 도구가 생겨도 수정하는데 큰어려움이 없을 것같습니다.
(2) 유연성? 성능?
- 새로운 도구들이 추가될때마다 마우스 이벤트함수들을 수정해주면 유연성(재사용)면에서 좋을 것 같습니다.
- 하지만 도구들의 종류가 많아지게되면 “마우스 이벤트가 일어날때마다”
if, else
로 도구들을 판별하는 과정도 부담스럽게 될 것입니다. (단순히 마우스를 움직이기만해도if, else
로 도구를 탐색…) - 이렇게 성능적으로 생각한다면 도구를 선택함과 동시에 그 도구전용 이벤트를 설정하는 것이 더 좋을 것 같습니다.
(3) 이벤트 통제함수 구현
- 아직 이벤트함수에 관해 잘 모릅니다. (이벤트 위임, 버블 등등 들어봤지만 앞으로 배워야할 부분)
- 그래도 이벤트 추가(add), 제거(remove)함수에 대해서는 알기 때문에 이것들을 이용하여 다음과 같이 이벤트를 통제하는 함수(master)를 만들었습니다.
master
함수가 호출되면이전 이벤트 삭제 를 하고 도구에 맞는 새로운 이벤트를 생성하는 과정이 일어납니다.- 추가, 제거함수들에 각각의 도구에 맞는 이벤트 함수들을 적용해야되기 때문에 처음에 함수를 만들고 추가하는 과정이 번거로울 수 있지만 도구 추가,제거가 편해질 것 같습니다.
- 무엇보다 마우스 이벤트마다
도구를 판별 할 필요가 없어집니다.
if (paint) {
paint.addEventListener("click", () => master(PAINT));
}
if (pen) {
pen.addEventListener("click", () => master(PEN));
}
- 위의 코드처럼 도구를 선택하면
master
함수가 호출되어 알맞는 이벤트로 적용됩니다.
(4) 보완해야될 점(클래스화)
- 각 도구마다 겹치는 기능들이 있을 것같은데 그것에 대한 처리가 아쉬운 것 같습니다.
- OOP적인 관점으로 접근한다면 각 도구들마다 클레스를 구현하고 겹치는 기능들은 상속을 이용하여 구현하면 좀 더 깔끔하지 않을까 하는 생각이 들었습니다.
- 자바스크립트에서 클레스(class)를 이용하는 방법도 공부해 봐야할 것 같습니다.
- 아직 클레스(class)사용이 서툴지만 이와 비슷하게 사용하기 위해서 다음과 같이 배열을 이용하여 정적 클래스처럼 작동하도록 만들어 봤습니다.
- 이것이 올바른 사용방법(클린코드)인지는 모르겠지만 자바스크립트에서는 함수를 배열안에 담을 수 있었습니다.
- 또한 이것을 정적 클래스처럼 사용할 수 있었습니다.
- 주의할점은 함수가 const 변수안에 담기므로
호이스팅(hoisting) 이 되지않습니다. (hoisting이란 js에서 var변수, 함수선언이 자동으로 위쪽으로 배치되는 것)
하지만 이것은 어디까지나 나만의 방법으로 작성한 코드입니다. 좋은습관을 잡기위해서는 하루빨리 “js 클린코드 작성하는 법”을 공부해야될 것 같습니다.
4️⃣ 새로운 기능 추가
(1) 디테일 색상툴 이용하기
- html의
input
태그중에 다음과 같이 디테일하게 색상을 설정할 수 있는 기능이 있었습니다.
- “input”이라는 이벤트를 사용하면 됩니다.
- 기본 그림판에서는 이미 다음과 같은 색상툴이 있었습니다.
- 이 색상툴의 원리는
style.backgroundColor
로 접근해 색을 가져오는 것 입니다. - 이와 다르게 새롭게 추가한 색상툴은
.value
로 색을 가져옵니다.
- 즉, 두가지 색상툴의 핸들링 함수는 색상을 가져오는 부분만 다를뿐 나머지 코드는 중복됩니다.
- 위와같이 하나의 함수로 묶어줬습니다.
(2) 현재색상 표시기능
- 위와같이 두가지 버전 색상툴을 사용하다보니 현재
어떤색을 사용 하는지 햇갈렸습니다.
- 위의
(1)
에서 만들어준 색상 핸들링 함수(changeColorFunc)에서 색상을 업데이트해주도록 했습니다.
(3) 반응형 종이
- 기본 그림판은 캔버스 종이의 크기가 고정된 값입니다. 이것을 반응형 종이로 만들었습니다. (반응형 css요소설정 설명은 생략)
- canvas API는 다음과 같은 초기 설정이 필요합니다.
- 이렇게 생성된 ctx는
canvas
노드의 width, height요소에 의존적입니다. - 그렇기 때문에 크기값이 변하는 반응형 종이를 사용하기 위해서는 다음과 같이 “resize”이벤트를 이용하여 width, height를 업데이트 시켜줘야 합니다.
window.addEventListener("resize", () => {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
});
- 하지만
"resize" 이벤트가 일어나면 기존에 그림판 내용들이 지워졌습니다. - 그림판 내용이 지워지지 않게 하기위해서는 그림판에 그림을 그릴때마다 데이터를 저장해주고
"resize" 이벤트가 일어나면 다시 저장된 데이터를 불러서 그려주는 작업을 하면 될 것 같습니다. 하지만 코드를 어떻게 구현하지는 아직 제 능력 밖인 것같습니다. 그리고 결론적으로 비효율적인 방법인 것 같고 다른 방법을 찾아봐야할 것 같습니다.
(8.27 추가내용)
- 아무래도 캔버스 종이의 크기는 반응형보다는
고정된 값 을 사용하는 것이 좋을 것 같습니다. - 그림같은경우 각자만의 수치로 그림을 그릴텐데 반응형으로 수치가 변하는 것은 말이 안됩니다.
- 대신에 다른 툴들은 반응형으로 크기가 변할 수 있게 놔두었습니다.
- 만약 디바이스에 종류에 따라 정밀하게 크기를 지정하고 싶으면 css의
"@media" 기능을 이용하여 커스텀해주면 될 것 같습니다. - 결론적으로 반응형화면도 무조건적으로 사용하기보다는 상황에 따라 알맞게 사용해야될 것 같습니다.