DEVLOG

HTML5 캔버스(Canvas)기초, 캔버스로 그림판만들기 본문

dev log/html canvas

HTML5 캔버스(Canvas)기초, 캔버스로 그림판만들기

meroriiDev 2020. 12. 14. 17:48
728x90
반응형

눈으로 보여지는걸 만드는게 좋아서 선택한 길이다보니 이왕 하는거 제대로 만들어보자!하는 생각이 들었다.

그래서 인터렉티브한 웹을 만드는데 기본이 되는 html5 캔버스에 대해서 공부해보려고한다.

사실 빠르게 이미 만들어진 예제의 코드를 분석해고 이것저것 해볼까 했지만...........

그냥 기본부터 차근차근 쌓아나가기로 했다!

 


Canvas API란?

JavaScript와 HTML <canvas> 엘리먼트를 통해 그래픽을 그리기위한 수단을 제공합니다. 무엇보다도 애니메이션, 게임 그래픽, 데이터 시각화, 사진 조작 및 실시간 비디오 처리를 위해 사용됩니다.
Canvas API는 주로 2D 그래픽에 중점을 두고 있습니다. WebGL API 또한 <canvas> 엘리먼트를 사용하며, 하드웨어 가속 2D 및 3D 그래픽을 그립니다.

=> 즉, Canvas API는 비디오편집이나 이미지 조작을 하는데에 일반적인 html로 표현이 불가능한 표현들을 할 수 있다.

 

 

캔버스를 지원하는지 확인하기

먼저 이 페이지를 사용하는 브라우저가 캔버스태그를 지원하는지 확인해야한다. 미리 확인하고 이에 대한 대응도 마련해두어야 한다!

1. 모더나이저(Modernizr)

<script src="./modernizr.js"></script>

....

// Canvas 지원 여부 확인
if (Modernizr.canvas) {
	// console.log('Canvas를 지원하는 브라우저');
}

브라우저의 기능들을 조사해서 이 브라우저에서 기능을 지원하는지 안하는지 판별해줄 수 있다.

 

modernizr.com/download?setclasses

 

Modernizr Download Builder

Detects support for the DOM Pointer Events API, which provides a unified event interface for pointing input devices, as implemented in IE10+, Edge and Blink.

modernizr.com

위 링크에서 사용이 가능한지 체크하길 원하는 기능들을 체크체크 한 후 빌드하면 필요한 파일들만 체크하는 js파일이 생성된다!

다운받아서 포함해서 사용하면 된다.

 

2. 메소드확인

모더나이저를 사용하지 않고 간단하게 코드로 확인하는 방법도 있다.

const canvas = document.querySelector('canvas');
if (canvas.getContext) {
	console.log('캔버스 지원');
}

캔버스 객체를 가져온후 캔버스의 대표적인 메소드 (getContext)가 존재하는지로 판별하여 캔버스를 사용할 수 있는지 판별하면 된다.

 


캔버스 생성

우선 캔버스라는 것은 말그대로 도화지를 의미한다.

가장 밑바탕인 도화지를 만들고 그 위에 선을 긋고, 그림을 그려나가는 것이다.

먼저 가장 기본적인 캔버스를 생성해보자!

 

<canvas class="canvas" width="600" height="400"></canvas>

캔버스태그에 width값과 height값을 넣어 생성하면 되는데 이때 아무값도 넣지않고 생성하면

기본값 300x150 크기로 생성이 된다.

 

캔버스의 크기를 css에서 조절할 수도 있지만 html에서 조절하는거랑 css에서 조절하는 거랑 의미가 조금 다르다.

예를 들어 width가 500인 캔버스를 생성한다고 하자

 

좌 html에서 / 우 css에서

좌측 캔버스는 html canvas태그에서 width를 500으로 생성해주었고,

우측 캔버스는 html canvas태그에서 width를 1000으로 생성해주고 css에서 width를 500으로 줄여주었다.

눈으로 보이는 캔버스 크기는 500으로 동일해 보이지만 실제로는 1000의 값을 갖고 있는 애를 억지로 줄인거라 좌표가 다른 값을 갖게된다.

 

* 단, 고해상도로 표현할때는 위와 같이 표현하기도 한다. 

캔버스를 두배의 크기로 생성한 후 css에서 표현하고자 하는 크기로 줄이게 되면 고해상도의 이미지를 쉽게 표현해낼 수 있다. 대표적으로 고해상도 이미지를 많이 사용하는 애플에서도 캔버스를 많이 사용하는데 이런식으로 두배로 늘린다음에 css로 줄여서 나타낸다고 한다.

==> 이미지표현에 좋긴한데.. 픽셀개수가 많아져서 성능은 떨어질 수도 있다는 단점이 있다.

 


시작

캔버스 위에 작업을 할때에는 캔버스의 context의 객체를 얻어와서 작업을 한다. => getContext();

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

위 코드는 캔버스를 가져와서 캔버스의 객체를 얻어오는 과정으로 모든 과정에서 꼭 불러와줘야한다.

 

그리기

1. 사각형그리기

(1) 스타일주기

context.fillStyle="색상";

사각형을 만들기 바로 윗줄에서 fillSytle로 스타일을 설정해줄수도 있다.

 

(2) 안이 채워진 사각형

context.fillRect(x, y, width, height);

(3) 선만 그려지는 사각형

context.strokeRect(x, y, width, height);

(4) 사각형영역 지우기

context.clearRect(x, y, width, height);

안이 채워진 사각형을 그리려면 fillRect를, 안이 뚫린 선만 있는 사각형을 그리려면 strokeRect를, 해당부분을 사각형영역으로 지우려면 clearReact를 사용하면된다.

파라미터는 모두 (시작x좌표, 시작y좌표, 너비, 높이)를 의미한다.

 

2. 직접 그리기

(1) 그리기의 시작선언 ★

context.beginPath();

앞으로 모든 그리기를 하기 이전 beginPath를 선언해주는것을 잊지말자.

원래는 beginPath로 그리기를 시작하고, closePath로 그리기를 끝내주는것이 한세트이지만

beginPath만 제대로 선언해줘도 알아서 이전 작업을 끝내고 새 작업을 시작할 수 있다.

beginPath를 제대로 선언해주지 않으면 위 사진의 좌측사진처럼 마치 한붓그리기처럼 만들어지게 된다!

 

(2) 시작점으로 이동

context.moveTo(x,y);

(3) 종료지점으로 이동

context.lineTo(x,y);

(4) 시작점부터 종료지점까지 선그리기

context.stroke();

(5) 시작점부터 종료지점까지 채우기

context.fill();

(6) 그리기 종료

context.closePath();

 

* arc() 호그리기 메소드 (원그리기)

arc(x, y, r, 시작각도, 끝각도, true/false);

arc(중심점x, 중심점y, 반지름, 시작각도, 끝각도, true/false);

* 가장 마지막 파라미터는 방향을 의미한다. (true : 시계방향 / false:반시계방향)

* 사용되는 각도는 라디안각도를 써야한다!

360 = 2π 이므로 360이 아니라 2π라고써주어야한다.

이때, 스크립트로 π를 나타내는 표현은 Math.PI이다.

 

매번 쓰기 귀찮다면 각도를 계산해주는 함수를 만들어놓고 사용하자

function 라디안(각도){
	return 각도 * Math.PI / 180;
}

 


애니메이션

캔버스에서의 애니메이션이란

지우고옮기고그리고지우고옮기고그리고지우고옮기고그리고..........................의 반복이다!

따라서 반복을 시키는 것이 중요한데 반복시키는데에 두 가지의 방법이 있다.

 

1. requestAnimationFrame()

: 기다렸다가 그리기가 최적화되었을때 그리게 해주는 메소드

function draw(){

	...
	requestAnimationFrame(draw);
}

requestAnimationFrame(그리는 메소드);

그리는 메소드 안에서 그 메소드를 다시 호출하면서 계속 반복반복 무한루프가 돌게 된다.

이때 그냥 무작정 와다다다 호출하는 것이 아니라 requestAnimationFrame메소드를 이용하여 최적화상태로 준비되어있을때만 호출하는 것이다.

 

2. setInterval

function draw() {
	...
}

timerId = setInterval(draw, 500);
canvas.addEventListener('click', () => {
	clearInterval(timerId);
});

setInterval(그리는 메소드, 진행시간);

requestAnimationFrame을 쓰면 인터벌을 조절할 수 있는 기능은 따로 없지만 setInterval는 인터벌을 조절할 수 있다.

하지만 setInterval은 최적화될때까지 기다리는게 아니라 무작정 다 때려넣어서.. 성능면에서 떨어진다!

그래서 대부분 reqestAnimationFrame을 쓰는 추세이다. 단, 인터벌을 조절할 수 있는 장점은 분명 있다......

뭐야 암튼 둘다 쓸모가 있는 아이들이니 둘 다 알아둘 필요가 있다고 한다......

 

애니메이션중지

지금 만들어진 코드는 애니메이션이 무작정 계속 실행이 되기때문에

일정 조건이 되면 애니메이션이 중지하도록해야한다.

 

(1) requestAnimationFrame을 사용할 때의 애니메이션 중지

if(xPos >= canvas.width){
	return;
}
let timerId;

function draw(){
  ...
  timerId = requestAnimationFrame(draw);
  if(xPos>= canvas.width-10){
      cancelAnimationFrame(timerId);
  }
  ...
}

(2) setInterval일때 애니메이션중지

timerId = setInterval(draw, 500);

canvas.addEventListener('click', () => {
	clearInterval(timerId);
});

 


이미지

이미지라는 외부데이터는 로딩되는데에 시간이 필요하다.

캔버스에 그리려면 이미지 로딩이 끝난후 그릴 수 있으므로 로딩이 되는동안 기다려야한다

==> addEventListener('load',()=>{})

const imgElem = document.createElement('img');
imgElem.src = '이미지경로';
imgElem.addEventListener('load', ()=>{
	이미지생성
}

따라서 위처럼 img태그를 생성하고, src를 설정한후 이벤트리스너를 추가하여 그 안에 아래처럼 이미지를 생성해야한다.

context.drawImage(이미지객체, 시작x, 시작y);	//원본사이즈로
context.drawImage(이미지객체, 시작x, 시작y, width, height);	//사이즈조절
context.drawImage(이미지객체, sx, sy, swidth, sheight, dx, dy, dwidth, dheight ); //이미지 일부만 crop

 

 


그림판만들기 예제

<!DOCTYPE html>
<html>
  <head>
    <title>Canvas</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      canvas {
        background: #eee;
      }
      .color-btn {
        width: 30px;
        height: 30px;
        border: 0;
        border-radius: 50%;
      }
      .color-btn[data-color='black'] { background: black; }
      .color-btn[data-color='red'] { background: red; }
      .color-btn[data-color='green'] { background: green; }
      .color-btn[data-color='blue'] { background: blue; }
      .image-btn {
        width: 40px;
        height: 40px;
        background: url(../images/ilbuni2.png) no-repeat 50% 50% / cover;
      }
      .result-image {
      }
    </style>
  </head>

  <body>
    <h1>Image</h1>
    <canvas class="canvas" width="600" height="400">이 브라우저는 캔버스를 지원하지 않습니다.</canvas>
    <div class="control">
      <button class="color-btn" data-type="color" data-color="black"></button>
      <button class="color-btn" data-type="color" data-color="red"></button>
      <button class="color-btn" data-type="color" data-color="green"></button>
      <button class="color-btn" data-type="color" data-color="blue"></button>
      <button class="image-btn" data-type="image"></button>
    </div>
    <button class="save-btn">이미지 저장</button>
    <div class="result-image"></div>

    <script>
      const canvas = document.querySelector('.canvas');
      const context = canvas.getContext('2d');
      const control = document.querySelector('.control');
      const saveBtn = document.querySelector('.save-btn');
      const resultImage = document.querySelector('.result-image');
      let drawingMode; // true일 때만 그리기
      let brush = 'color'; // 'color', 'image'
      let colorVal = 'black'; // 색상

      const imgElem = new Image();
      imgElem.src = '../images/ilbuni2.png';

      function downHandler() {
        drawingMode = true;
      }

      function upHandler() {
        drawingMode = false;
      }

      function moveHandler(이벤트) {
        if (!drawingMode) return;

        switch (brush) {
          case 'color':
            context.beginPath();
            context.arc(이벤트.layerX, 이벤트.layerY, 10, 0, Math.PI*2, false);
            context.fill();
            break;
          case 'image':
            context.drawImage(imgElem, 이벤트.layerX, 이벤트.layerY, 50, 50);
            break;
        }
      }

      function setColor(이벤트) {
        brush = 이벤트.target.getAttribute('data-type');
        colorVal = 이벤트.target.getAttribute('data-color');
        context.fillStyle = colorVal;
        console.log(brush);
      }

      function createImage(){
        const url = canvas.toDataURL('image/png');
        console.log(url);
        const imgElem = new Image();
        imgElem.src = url;
        resultImage.appendChild(imgElem);
      }

      canvas.addEventListener('mousedown', downHandler);
      canvas.addEventListener('mousemove', moveHandler);
      canvas.addEventListener('mouseup', upHandler);
      control.addEventListener('click', setColor);
      saveBtn.addEventListener('click', createImage);
    </script>
  </body>
</html>

* clientX,Y =>브라우저위치기준

   layerX,Y =>캔버스위치기준

 

* image저장

toDataURL('image/png')

 

설명은 유튜브 참고하기

 

 비디오

<video></video>

흔히 알고있는 video태그를 사용하긴 하지만 일반 video태그와 다른 점은

비디오를 잘게 쪼개서 조합하거나 색을 넣거나 하는 작업이 가능하다는 것이다.

 

<video autoplay muted></video>

* 브라우저마다 다른데 크롬 정책상 소리가 나는 영상은 기본적으로 autoplay가 불가능하다.

그래서 autoplay, muted를 함께 사용해야 자동 재생이 가능하다.

 

canplaythrough

videoElem.addEventListener('canplaythrough', render);

이미지는 화면이 load되면 발생하는 load이벤트를 사용하고

비디오는 재생준비가 되면 발생하는 canplaythrough이벤트를 사용한다.

비디오도 이미지와 마찬가지로 반복적으로 함수를 호출하는 반복재생을 통해 작동된다.

 

video.currentTime

for (let i = 0; i < messages.length; i++) {
  if (videoElem.currentTime > messages[i].time) {
  	ctx.fillText(messages[i].message, messages[i].x, messages[i].y);
  }
}

비디오의 현재 시간을 알아낼 수 있는 메소드이다.

자막효과와 비슷한 기능을 나타내는 것으로 사용할수도 있을 것 같다.

 

비디오에 색상필터씌우기

그냥 그 위에 opacity를 줄인 div를 덮어씌우면 안돼..?굳이..?라고 생각했었지만!

다른 색을 죽이거나 위에 필터를 덮어씌우는게 아니라

해당하는 색상의 비율을 세게 높여서 그 색으로 보이도록 만들어주는 것이다.

 

getImageData <-> putImageData

각 픽셀의 색상데이터를 가져오는 메소드

{data배열[픽셀의 R, G, B, A], width, height}를 가져옴!

 

따라서 이 기능을 이용해서 이미지가 먼지처럼 스르르 퍼지게 하는 효과도 구현할 수 있다!

기회가 되면 꼭꼭 해봐야지:)

 

○ Transform

위치이동, 회전, 크기변환시키는 것

 

변환초기화

** 초기에 단위행렬로 변환초기화하기

ctx.setTransform(1,0,0,1,0,0)
= ctx.resetTransform()

: 변환초기화를 하지않으면 원하는대로 변환이 제대로 이루어지지 않을 수 있으니

변환초기화하는 것을 꼭 습관을 들이자!

 

기준점 바꾸기

캔버스에서 default기준점은 왼쪽상단이다

가운데에서부터 커지도록하기 위해 기준점을 변경해주려면 다음과 같이 변경해줘야 한다.

ctx.translate(x,y);

 

상태(state)의 저장과 복원

save()

canvas의 모든 상태를 저장

restore()

가장 최근에 저장된 canvas상태를 복원

 


www.youtube.com/watch?v=JFQOgt5DMBY

www.youtube.com/watch?v=ovf8cbKtBH0

 

728x90
반응형

'dev log > html canvas' 카테고리의 다른 글

HTML5 캔버스(Canvas) 화면에 튕기는 공 만들기  (0) 2020.12.15
Comments