2021. 11. 30. (화) 바닐라 JS로 그림판 만들기

2021. 12. 1. 00:40NomadCoders/바닐라 JS로 그림판 만들기

728x90
반응형

# 학습 전 나의 상태

11월 마지막 날 오랜만에 블로그 작성 중이다.

노마드 코더에서 2개의 챌린지를 진행하느라 약간 정신이 없어 블로깅을 하지 못했다.

코코아 클론 챌린지는.. 실패했다... 중간에 과제 1번 미제출.. 마지막 졸업 작품.. gitgub pages 를 만들지 못해.. 미제출..ㅜㅜ

그렇지만 바닐라 JS는 성공했다. 허접하지만.. 나름 열심히 했다.. (흑..ㅠㅠ)

https://kimoony.github.io

오늘은 바닐라 JS와 Canvas를 공부해보고 싶어 그림판 만들기 강의를 보기로 했다.

 

 

# 오늘의 학습 내용

1. Canvas Event

  • mousemove 이벤트를 적용하고 함수를 만들어 Canvas 안에서 event가 발생했을 시 console.log를 찍어보았다. 
  • client 값은 윈도우 전체의 범위 내에서 마우스 위치값을 나타내는데
  • 나는 여기서 Canvas 내 좌표가 있는 offset 값이 필요하다.
  • offset을 적용한 코드이다.
    const canvas = document.getElementById('jsCanvas');
    
    
    const onMouseMove = (e) => {
      const x = e.offsetX;
      const y = e.offsetY;
      console.log(x, y);
    }
    
    if (canvas) {
      canvas.addEventListener("mousemove", onMouseMove)
    }
  • console.log(x, y)를 한 결과는 아래와 같다.
  • Canvas Event 코드
    const canvas = document.getElementById('jsCanvas');
    
    // 기본적으로 painting은 false 이다.
    let painting = false;
    
    const stopPainting = () => {
      painting = false;
    }
    
    const onMouseMove = (e) => {
      const x = e.offsetX;
      const y = e.offsetY;
    }
    
    // 마우스를 클릭했을 때 painting은 true가 된다.
    const onMouseDown = (e) => {
      painting = true;
    }
    
    // 마우스 클릭을 땠을 때 다시 false가 된다.
    const onMouseUp = (e) => {
      stopPainting()
    }
    
    if (canvas) {
      canvas.addEventListener("mousemove", onMouseMove)
      canvas.addEventListener("mousedown", onMouseDown)
      canvas.addEventListener("mouseup", onMouseUp)
      canvas.addEventListener("mouseleave", stopPainting)
    }​

 

2. Context

  • canvas는 HTML에 의한 요소인데 다른 점은 context를 갖는 다는 것이다.
  • Context는 canvas 요소 안에 픽셀에 접근할 수 있는 방법
  • stokeStyle이란?
    • 도형 주위의 선에 사용할 색상 또는 스타일
  • lineWidth는 input:range 즉, 라인의 굵기
  • context의 paths는 기본 선, 선의 시작점을 의미한다.
  • context 코드
    const canvas = document.getElementById('jsCanvas');
    const ctx = canvas.getContext('2d');
    
    // canvas에게 width와 height를 알려준다.
    canvas.width = 500;
    canvas.height = 400;
    
    // 선의 색
    ctx.strokeStyle = "#2c2c2c";
    // 선의 너비
    ctx.lineWidth = 2.5;
    
    // 기본적으로 painting은 false 이다.
    let painting = false;
    
    const stopPainting = () => {
      painting = false;
    }
    
    const startPainting = () => {
      painting = true;
    }
    
    const onMouseMove = (e) => {
      const x = e.offsetX;
      const y = e.offsetY;
      if (!painting) {
        ctx.beginPath();
        // 움직는 동안 path가 만들어진다.
        ctx.moveTo(x, y);
      } else {
        // path의 이전 위치에서 지금 현재 위치까지 선을 이어준다.
        ctx.lineTo(x, y);
        // 현재의 sub-path를 현재의 stroke style로 획을 그음
        ctx.stroke();
      }
    }
    
    if (canvas) {
      canvas.addEventListener("mousemove", onMouseMove)
      canvas.addEventListener("mousedown", startPainting)
      canvas.addEventListener("mouseup", stopPainting)
      canvas.addEventListener("mouseleave", stopPainting)
    }

3. changing color

  • index.html 에 있는 컬러들의 클래스네임에 jsColor 추가
    <div class="controls__colors" id="jsColors">
          <div class="controls__color jsColor" style="background-color: #000"></div>
          <div class="controls__color jsColor" style="background-color: #fff"></div>
          <div class="controls__color jsColor" style="background-color: #ff3b30"></div>
          <div class="controls__color jsColor" style="background-color: #FF9500"></div>
          <div class="controls__color jsColor" style="background-color: #FFCC00"></div>
          <div class="controls__color jsColor" style="background-color: #4CD963"></div>
          <div class="controls__color jsColor" style="background-color: #5AC8FA"></div>
          <div class="controls__color jsColor" style="background-color: #0579ff"></div>
          <div class="controls__color jsColor" style="background-color: #5856D6"></div>
        </div>​
  • app.js에서 jsColor를 불러오고, console.log(colors) 찍어보기
  • 이것을 Array로 담아보자
  • Array를 forEach로 color를 돌린다. 그리고 addEvevtListner("click", hadleColorClick) 함수를 호출한다.
  • hadleColorClick 함수에서 event 발생시 style.backgroundColor를 console로 확인해보니 rgb 값들이 나온다.
  • 이 값을 color라는 변수에 담아주고 윗줄에 ctx.strokeStyle에 color를 할당해주면 색이 클릭했을 때 해당 색으로 변경된다.
    const colors = document.getElementsByClassName('jsColor');
    
    const handleColorClick = (e) => {
      const color = e.target.style.backgroundColor;
      ctx.strokeStyle = color;
    }
    
    // Array로 만들고 forEach로 color를 돌려서 addEventListener("click", handleColorClick)를 호출
    Array.from(colors).forEach(color =>
      color.addEventListener("click", handleColorClick));​

 

4. Range와 Mode

  • 먼저 아래 사진의 range가 늘어나고 줄어듬에 따라 브로쉬의 사이즈가 변경이 되어야 한다.
  • 이벤트를 호출함 함수를 만들어준다.
  • 이벤트 발생시 찾아야 할 값은 event.target 안에 value 값이다.
  • 그래서 event.target.value를 변수에 담아주고 ctx.lineWidth에 담아준다.
    const range = document.getElementById('jsRange');
    
    const handleRangeChange = (e) => {
      console.log(e)
      const size = e.target.value;
      ctx.lineWidth = size;
    }
    
    if (range) {
      range.addEventListener("input", handleRangeChange);
    }​
  • Mode도 역시 먼저 이벤트 함수 호출을 해준다.
  • 그리고 전역에 let filling = false; 라는 변수를 만들어준다.
  • mode를 변경하는 함수에 filling이 false이면 false와 버튼에 FILL이 보이도록하고
  • filling이 true면 버튼에 PAINT가 나오도록 해준다.
    const mode = document.getElementById('jsMode');
    
    let filling = false;
    
    const handleModeClick = () => {
      if (filling === true) {
        filling = false;
        mode.innerText = "Fill"
      } else {
        filling = true;
        mode.innerText = "Paint"
      }
    }
    
    if (mode) {
      mode.addEventListener('click', handleModeClick);
    }​
  • 이제 FILL버튼을 누르고 색상을 선택하여 canvas에 클릭하면 전체 색상이 변경 되도록 해보자
  • fillRect()를 사용해 전체 width, height 값만큼 색을 입힐 수 있다.
  • fillstyle()은 색상을 변경해준다.
    const INITIAL_COLOR = "#2c2c2c";
    
    canvas.width = 500;
    canvas.height = 400;
    
    ctx.fillStyle = INITIAL_COLOR;
    
    const handleCanvasClick = () => {
      if (filling) {
        ctx.fillRect(0, 0, canvas.width, canvas.height);
      }
    }​

 

5. 이미지로 저장하기

  • Canvas는 pixel을 다루고 기본적으로 image를 만든다.
  • 그래서 다운로드나 저장은 이미 내장되어 있다.
  • 이미지로 저장하기 위해 canvas.toDataURL('image/png'); 를 입력한다.
  • document.createElement로 a 태그를 만들어주고 클릭시 다운로드 되도록 한다.
    const save = document.getElementById('jsSave');
    
    // 우클릭으로 저장하기 방지
    const handleCM = (e) => {
      e.preventDefault();
    }
    
    const handleSaveClick = () => {
      const image = canvas.toDataURL('image/png');
      const link = document.createElement('a');
      link.href = image;
      link.download = 'PaintJS';
      link.click();
    }
    
    if (canvas) {
      canvas.addEventListener("contextmenu", handleCM);  // 우클릭 방지
    }
    
    if (save) {
      save.addEventListener('click', handleSaveClick);
    }​

 

참고자료

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D

 

CanvasRenderingContext2D - Web APIs | MDN

The CanvasRenderingContext2D interface, part of the Canvas API, provides the 2D rendering context for the drawing surface of a <canvas> element. It is used for drawing shapes, text, images, and other objects.

developer.mozilla.org

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

 

Canvas API - Web API | MDN

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

developer.mozilla.org

https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL

 

HTMLCanvasElement.toDataURL() - Web APIs | MDN

The HTMLCanvasElement.toDataURL() method returns a data URI containing a representation of the image in the format specified by the type parameter (defaults to PNG). The returned image is in a resolution of 96 dpi.

developer.mozilla.org

 

 

 

# 학습을 마치며

평소 Canvas가 궁금했다. 왜냐하면 파이널 프로젝트때 그림일기 만드는 것에서  동기분이 canvas를 활용했기 때문이다.

그런데 이렇게 좋은 강의가 있어서 canvas 기본에 대해 배울 수 있었던 것 같다.

이걸 어떻게 써먹을지.. 고민 좀 해봐야겠다.

그리고 내일부터는 React를 다시 공부하려고 한다.

한동안.. 바닐라JS만 했더니.. React가 가물..ㅜ 다시 학습하고 담주부터 챌린지에 참여할 것이다.

마지막이라는 각오로.. React와 JS를 정복하고 싶다..

728x90
반응형