JavaScript로 Canvas 만들어 보기

seul_velog·2021년 12월 19일
2

강의를 보며 HTML + CSS + JavaScript로 그림판을 만들어 보았다.


✍️ 작성메모

HTML + CSS

1) Canvas API

  • Canvas API 는 JavaScript와 HTML <canvas> 엘리먼트를 통해 그래픽을 그리기위한 수단을 제공한다. 주로 2D그래픽에 중점을 두고 있으며, 애니메이션, 게임 그래픽, 데이터 시각화, 사진 조작 및 실시간 비디오 처리를 위해 사용된다.

2) box-shadow 속성

  • box-shadow 는 요소의 테두리를 감싼 그림자 효과를 추가한다. , 로 구분해서 여러 그림자 효과를 입힐 수 있다. 박스 그림자는 요소에서의 수평수직 거리(오프셋), 흐릿함과 확산 정도, 색상으로 이루어진다.
/* offset-x | offset-y | blur-radius | spread-radius | color */
box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);

3) all: unset

  • 요소의 모든 속성을, 속성이 값을 상속하는 경우 상속값으로, 아니면 초깃값으로 변경한다.
  • button에 사용함으로써 브라우저마다 기본적으로 적용되어 있는 버튼의 기본 스타일 값을 초기화 하고 자유롭게 커스텀할 수 있었다. 만약 각각 설정해 주었다면 유튜브UI때처럼 일일이 작성을 해줬을 것이다.
  • MDN_CSS_all 참고

4) range

  • input 태그의 type 속성값 중 하나이다.
  • <input type=“range”>는 슬라이드 바를 조정하여 범위 안의 숫자를 선택할 수 있는 입력 필드를 정의한다.
  • range 비교적 정확하지 않은 값을 제어할 때 사용한다. (스크롤)
  • 기본 범위는 0~100이지만 직접 지정 가능하다.
    • max : <input> 요소의 최댓값을 명시한다.
    • min : <input> 요소의 최솟값을 명시한다.
    • step : <input> 요소에 입력할 수 있는 숫자들 사이의 간격을 명시한다.
    • value : <input> 요소의 초깃값(기본값)을 명시한다.
  • MDN_range 참고




JavaScript

1) getElementById() 메서드

  • 주어진 문자열과 일치하는 id 속성을 가진 요소를 찾고, 이를 나타내는 Element 객체를 반환한다. ID는 문서 내에서 유일하므로 특정 요소를 빠르게 찾을 때 유용하다.

2) 마우스 이벤트

click : element를 클릭했을 때(버튼을 눌렀다가 떼었을 때) 발생 한다.
mousedown : element에서 마우스 버튼을 눌렀을 때 발생한다.
mouseup : element에서 눌렀던 마우스 버튼을 떼었을 때 발생한다.
mousemove : element에서 마우스를 움직였을 때 발생한다.
mouseover : element 바깥에서 안으로 옮겼을 때 발생한다.
mouseout : element 안에서 바깥으로 옮겼을 때 발생한다.

  • console에서 clientX,Y는 윈도우 전체의 범위 내에서 마우스 좌표값을 나타내고, offsetX,Y는 캔버스를 기준으로 한 좌표값이다.

3) getContext

  • <canvas>context 를 갖고 있는 HTML의 요소이고, context 는 이 요소 안에서 우리가 픽셀에 접근할 수 있는 방법이다. (내부의 픽셀들)
  • context 가 가지고 있는 기능들 MDN_Context
function onMouseMove(event){
    const x = event.offsetX;
    const y = event.offsetY;
  
    if(painting===false){ // if(!painting) 
        ctx.beginPath(); //경로 생성
        ctx.moveTo(x, y); //선 시작 좌표
    }
    else{
        ctx.lineTo(x, y); //선 끝 좌표
        ctx.stroke(); //선 그리기
    }
}

4) JS에서 cavas 사이즈 가져오기

  • canvas element는 두 가지 사이즈를 가져와야한다. (1) CSS 사이즈, (2) 픽셀을 다룰 사이즈 (pixel manipulathing size)
  • 그런데 JS가 html로부터 jsCanvas라는 id값을 바탕으로 canvas라는 element를 생성할 때 css에서 지정한 width와 height는 받아오지 않는다. 이 때 사이즈를 지정하거나 가져오는 방법은 아래와 같다.
// JS에서 픽셀을 다룰 캔버스사이즈 가져오기
// ex.1 ) JS에 명시한다.
canvas.width = 700;
canvas.height = 700;

// ex.2 ) JS에서 가져온다.
canvas.width = document.getElementsByClassName("canvas")[0].offsetWidth;
canvas.height = document.getElementsByClassName("canvas")[0].offsetHeight;

canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;

// ex.3 ) HTML에 지정한다.
<canvas id="jsCanvas" width="700" height="700"
  • ex. 1) 의 방법대로 가져오면 오류가 생길 여지가 있으니 아래처럼 경로를 명확하게 지정해 주는 것이 좋다고 한다.

5) 컬러변환
5-1 ) color 오버라이딩

const colors = document.getElementsByClassName("jsColor")

function handleColorClick(event){
    const color = event.target.style.backgroundColor;
    ctx.strokeStyle = color; 
}

Array.from(colors).forEach(color => color.addEventListener("click", handleColorClick))
  • 여기서부터는 strokeStyle이 target에 있는 색상이 된다. ( strokeStyle을 override함 )

5-2 ) Array.from 메서드

  • Array.from() 메서드는 유사 배열 객체(array-like object)나 반복 가능한 객체(iterable object)를 얕게 복사해 새로운Array 객체를 만든다.
  • 즉, object로부터 array를 만든다.

5-3 ) Array.prototype.forEach() 메서드

  • forEach() 메서드는 주어진 함수를 배열 요소 각각에 대해 실행한다.

5-4 ) 콜백함수 인자 명명

Array.from(colors).forEach
(color => color.addEventListener("click", handleColorClick))

여기서 color 는 다른이름으로 지정해도 상관없다. array안에 있는 각각의 아이템들을 대표하는 것이라고 보면 된다. (마치 div의 의미같이. div : 특정 영역이나 구획을 정의할 때 사용)

📌 Point !
콜백함수로 전달되어지는 인자는 최대한 이해하기 쉽도록 쓰는것이 중요하다.
콜백함수에서 전달되어지는 인자를 value , item, element 와 같이 이름지어서 전달한다면, 협업을 하거나 긴 코드를 작성할 때 이러한 이름이 많아지면 가독성이 떨어질 수 있다. 때문에 'student, color'와 같이 의미있는 이름을 짓는 것이 중요하다! 😃
배열 메서드(2)


5-5 ) getElementsByClassName 와 querySelector

  • document.getElementsByClassName("jsColor");
    Element의 메소드 getElementsByClassName() 는 주어진 클래스를 가진 모든 자식 엘리먼트의 실시간 HTMLCollection 을 반환한다.
    Document의 메소드 getElementsByClassName() 는 도큐먼트 루트로부터 도큐먼트 전체를 탐색한다는 점을 제외하고는 위와 동일하게 작동한다.

  • querySelector
    수 많은 컬러 div중 오직 제일 처음 div만 선택해서 결과적으로 color는 변하지 않는다. 이때 querySelector All 을 통해서 getElementsByClassName 와 같은 효과를 볼 수 있다.
    이 부분은 프로젝트를 벗어나 다시 따로 모아서 정리를 해야겠다. 🤔 DOM요소선택하기


6) HTML의 <input type="range"> 를 JS에서 활용하기

const range = document.getElementById("jsRange");

// input은 반응할 이벤트 유형
if(range){
    range.addEventListener("input", handleRangeChange);
}

function handleRangeChange(event){
    const size = event.target.value;
    ctx.lineWidth = size;
}
  • JS에서 getElementById로 요소를 가져온다.
  • EventTarget.addEventListener() API를 이용한다.
  • 지정된 타입 (input) 의 이벤트가 발생했을 때, 콜백되는 함수를 정의한다.
  • event.target.value 값을 변수size 에 할당한다.
  • ctx.lineWidth = size; 를 통해 상위 디폴트 값을 오버라이딩 한다.
    여기서 lineWidth 는 선의 너비를 제어한다. (CanvasRenderingContext2D.lineWidth)

8) mode.innerText = "Fill or Paint"

// html▼
<button id="jsMode">Fill</button>


// Javascript▼
const mode = document.getElementById("jsMode");

if(mode){
    mode.addEventListener("click", handleModeClick);
}

function handleModeClick(){
    if(filling === true){
        filling = false;
        mode.innerText = "Fill"
    } else {
        filling = true;
        mode.innerText = "paint"
    }
}
  • element.innerText;
    : 이 속성은 element 안의 text 값들만을 가져온다.

  • element.innerHTML;
    : innerText와는 달리 innerHTML은 element 안의 HTML이나 XML을 가져온다. 참고


9) Event.preventDefault() 메서드

  • 취소 가능한 경우 이벤트를 취소한다. 즉, 이벤트에 속한 기본 작업이 발생하지 않는다.
canvas.addEventListener("contextmenu", handleCM);


// (canvas에서 자체적으로 지원하는 우클릭 저장 방지 )
function handleCM(event){
    event.preventDefault()
}

10) 세이브 만들기

  • toDataURL()
    : HTML5, Canvas 에는 그려진 내용을 URL 문자열로 반환해 주는 함수가 제공된다. Canvas 객체의 toDataURL() 함수를 통해 캔버스에 그린 그림을 문자열 형태로 변환할 수 있다. 참고
    ✍️ Canvas 로 그린 내용을 문자열로 변환하여 img 요소의 src 로 사용하는 원리인 것 같다.

  • createElement
    : 지정한 tagName의 HTML 요소를 만들어 반환한다.

  • download() API
    : URL 등의 파일을 다운로드한다.

// html▼
<button id="jsSave">Save</button>


// Javascript▼
const saveBtn = document.getElementById("jsSave");

if(saveBtn){
    saveBtn.addEventListener("click", handleSaveClick)
}

function handleSaveClick(){
    const image = canvas.toDataURL();
  // jpeg를 원한다면 이렇게 설정한다. canvas.toDataURL("image/jpeg");
    const link = document.createElement("a");
    link.href = image;
    link.download = "Hello😎";
    link.click();
}
  • href는 img(URL)가 되어야 하고, download는 그이름을 가진다.


📌 Tip
+) num && console.log(num);
&& 연산자는 앞이 true 여야 뒤가 실행이된다. 앞이 false라면 뒷문장이 아예 실행이 되지 않는다. num에 값이 '있다면' num을 활용한 뒷문장을 사용할 수 있을 때 쓴다.

const num = 9;

if (num) {
console.log('true!');
}
// 9

num && console.log(num); 
// 9

//두개가 같은 의미이다.


✍️ 시행착오&변경
color 가져오기)
color를 css 스타일에서 각각 background로 부여하고 JS에서 컬러 동작 함수를 정의하고 싶었는데 getElementsByClassName을 통해서 가져오면 style이 가져와 지지 않았다. 그래서 queryselectorall로도 정의해봤지만 처음에 nodelist에 빈 배열이 담겨서 당황했고.. 알고 봤더니 HTML요소 명 앞에 . 을 붙이지 않아서였다. 😓 그런데 Nodelist를 확인해봤더니 여기에도 배경 속성이 오지 않았다. 결국 구글링을해서 getComputedStyle 키워드, 그리고 관련된 Element.style 키워드를 얻었다. 컬러를 각각 개별적으로 (위치등) 관리하기 위해서 시도해본 것이었는데, 사실 css에서 margin 속성으로 충분히 해결가능한 부분이다. 그리고 키워드에 대해서 알아보았다.Element_Style 가져오기

filling을 적용할 때 )
1. 클릭을 한 상태로 드래그를 하면 페인팅 선이보이고, 마우스를 up 하고 나서야 filling이 적용 되는 문제를 해결해야 했다. 먼저, mousedown인 상태가 filling 모드에서 작동하지 않게 하고 싶었다. mousedown인 상태는 즉 startpainting 함수가 호출된다는 것이고, 이 함수는 painting이 true(마우스 동작 관련 함수)로 이루어진다. 결국 filling 모드가 true 일때는 이것을 비활성화 시키면 되었다.
2. 그런데 위와 같이 하면 작동이 잘 되는 듯 보이지만, 클릭을 하고 있는 상태(드래그) 에서는 스트로크가 보이지 않을 뿐, filling모드가 아닌 painting모드였다. 그래서 두개의 작동 스위치는 결국 mousedown에 있으므로, canvas.addEventListener("mousedown", handleCanvasClick); 으로 수정 했다. 😃

Reset 버튼 추가하기)
같은 방식으로 리셋버튼을 추가해서 JS로 버튼을 작동시켰다. 사용한 기능은 CanvasRenderingContext2D.clearRect() 메소드이다. 기존에 정의한 캔버스 사이즈만 매개변수로 전달했는데 작동이 안되어서, x축과 y축 좌표값 0을 같이 지정했더니 잘 작동되었다 :)

+) 컬러버튼 동작효과 추가하기
버튼을 눌렀을 때 트랜스폼 스케일+트랜지션을 통해 선택효과를 시각적으로도 전달할 수 있도록 추가하였다.

profile
기억보단 기록을 ✨

1개의 댓글

comment-user-thumbnail
2023년 8월 21일

이쁘게 잘 만드셨네요ㅎㅎ 잘 보고 갑니다

답글 달기