[JS] JS에서 Canvas 사용하기(마우스로 그리기)

mokyoungg·2021년 4월 5일
6

Canvas란 무엇인가?

canvas는 HTML의 태그로서 캔버스 스크립트 API 또는 WebGL API와 함께 사용하여
그래픽과 애니메이션을 그릴 수 있습니다.
출처 : https://developer.mozilla.org/ko/docs/Web/HTML/Element/canvas

위의 설명대로, canvas는 web에서 그래픽, 애니메이션을 그릴 수 있게 도와주는 태그이다.
이 태그는 canvas API와 함께 사용하여 web에서 그림을 그리는 기능을 제공한다.

Canvas API

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

canvas API는 주로 2D 그래픽에 중점을 두고 있습니다. WebGL API 또한 canvas 엘리먼트를
사용하며, 하드웨어 가속 2D 및 3D 그래픽을 그립니다.
출처 : https://developer.mozilla.org/ko/docs/Web/API/Canvas_API

Canvas 사용 예시(in JS)

Canvas 세팅

초기의 코드는 위와 같다.

  • 먼저 캔버스 태그를 조작하기 위해서 canvas 변수에 canvas 태그를 할당하였다.
    (HTML canvas 엘리먼트에 대한 참조를 얻음.)
  • 이후 getContext() 메서드로 캔버스의 드로잉 컨텍스트를 받는다.
    해당 메서드의 파라미터로 2d를 전달하였는데 이를 통해 2차원 렌더링 컨텍스트 객체를 받게 된다.

HTMLCanvasElement.getContext()

구문
var atx = canvas.getContext(contextType)

contextType
캔버스에 연관된 드로잉 컨텍스트를 정의하는 컨텍스트 식별자를 갖는 DOMString 입니다.

위의 메서드는 캔버스의 드로잉 컨텍스트를 반환합니다.
컨텍스트 식별자가 지원되지 않을 경우 null을 반환합니다.
출처: https://developer.mozilla.org/ko/docs/Web/API/HTMLCanvasElement/getContext

getContext()메서드를 할당한 ctx를 기준으로 그림을 그리는 기능들이 작동한다고 생각하면 된다.

Width와 Height

위의 코드를 보면,
css 파일에서 캔버스 태그의 width와 height 크기를 설정하였는데 js파일에서도 canvas의 width와 height 크기를 설정한 것을 알 수 있다.

그래픽 캔버스 요소
width : 좌표 공간의 너비입니다. CSS 픽셀 단위로, 기본값은 300입니다.
height: 좌표 공간의 높이입니다. CSS 픽셀 단위로, 기본값은 150입니다.

캔버스 크기 조절 : CSS vs HTML
캔버스의 표시 크기는 CSS로도 수정할 수 있습니다.
그러나, CSS를 사용할 경우 렌더링 과정에서 CSS 크기에 맞추기 위해 이미지의 크기를 조절하므로, 최종 그래픽이 변형될 수 있습니다. 따라서 캔버스 태그의 width와 height 특성을 통해 직접 크기를 바꾸는 것이 좋습니다. HTML에서 직접 할 수도 있고, JS를 사용할 수도 있습니다.
출처 : https://developer.mozilla.org/ko/docs/Web/HTML/Element/canvas

캔버스의 크기를 css에서 지정하면 그래픽이 변형될 수도 있다고 한다.
예를 들어 마우스로 그림을 그릴 때 마우스로 그림을 그릴 때 커서의 위치와는 상관없이 다른 곳에 그림이 그려지는 경우가 있는데 이는 그래픽이 변형되었다고 할 수 있다.

HTML 또는 JS에서 캔버스의 크기를 설정하였다면 CSS에서 따로 크기를 설정할 필요는 없다.

캔버스 위 마우스 조작

캔버스 태그 위에서 발생하는 이벤트에 따라서 함수를 정의하였다.
마우스의 누르고 땠을 때는 경우에 따라 painting의 값이 true/false로 나뉜다.
이는 마우스를 누른 상태로 그림을 그리는 함수를 작성하기 위한 코드로 이후에 코드에서 painting의 값(마우스 상태)에 따라 그림을 그릴지 말지를 결정한다.

그림 그리기 함수 만들기

마우스 위치 확인

마우스로 그림을 그릴 때 제일 먼저 해당 마우스 커서의 위치를 알아야 한다.

마우스가 이동할 때마다 해당 event의 값을 console에 출력해보면 매우 다양한 값이 있다는 것을 알 수있다. 캔버스에서 마우스로 그림을 그릴 때 필요한 값은 offsetX와 offsetY 값이다.
offset 값 말고도 client, layer, page 등에서 x와 y값을 제공하지만 offset 값을 사용하는 이유는 offset의 값은 이벤트가 일어나는 대상에서의 x,y값이기 때문이다.

여기에선 이벤트가 일어나는 캔버스 위에서의 x와 y 값을 가리킨다.

MouseEvent.offsetX
The offsetX read-only property of the MouseEvent interface provides the offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node.
출처: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetX

그림 그리기

해당 내용의 출처 : https://developer.mozilla.org/ko/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes

경로를 이용하여 도형을 만들 때에는 몇가지 추가적인 단계를 거쳐야 합니다.

  1. 경로를 생성합니다.
  2. 그리기 명령어를 사용하여 경로상에 그립니다.
  3. 경로가 한번 만들어졌다면, 경로를 렌더링 하기 위해서 윤곽선을 그리거나 도형 내부를 채울 수 있습니다.

마우스로 그림을 그릴 때 4가지 메서드를 사용하였다.

  • beginPath()
  • moveTo()
  • lineTo()
  • stroke()

4가지 메서드는 getContext()를 할당한 ctx를 기준으로 작동한다.

beginPath()

CanvasRenderingContext2D.beginPath()

새로운 경로를 만든다.

경로가 생성됐다면, 이후 그리기 명령들은 경로를 구성하고 만드는데 사용하게 된다.
해당 메서드는 하위 경로 목록을 비움으로써 새 경로를 시작한다.

moveTo()

CanvasRenderingContext2D.moveTo()

펜을 x와 y로 지정된 자표로 옮깁니다.

실제로 어떤것도 그리지 않지만 경로의 일부가 된다. 이는 펜이나 연필을 종이위에서 들어 옆으로 옮기는 것으로 이해하면 된다.

캔버스가 초기화 되었거나 beginPath() 메서드가 호출되었을 떄, 특정 시작점 설정을 위해 moveTo() 함수를 사용하는 것이 좋다. 또한 moveTo() 함수는 연결되지 않은 경로를 그리는데에도 사용할 수 있다.

lineTo()

CanvasRenderingContext2D.lineTo()

현재의 드로잉 위치에서 x와 y로 지정된 위치까지 선을 그린다.

이 메서드는 선의 끝점의 좌표가 되는 x와 y의 두개의 인자가 필요하다.
시작점은 이전에 그려진 경로에 의해 결정되며, 이전 경로의 끝점이 다음 그려지는 경로의 시작점이 된다. 또한 시작점은 moveTo() 메서드를 통해 변경될 수 있다.

경로를 수정하는 다른 메서드와 마찬가지로 이 메서드는 직접 렌더링하지 않는다.
경로를 캔버스에 그리기 위해서는 fill() 또는 stroke() 메서드를 사용해야 한다.

stroke()

CanvasRenderingContext2D.stroke()

윤곽선을 이용하여 그린다.

해당 메서드는 현재 또는 지정된 경로를 현재 스트로크 스타일로 윤곽선을 그린다.
스트로크는 경로의 중앙에 정렬되는데 스트로크의 절반은 안쪽, 절반은 바깥쪽이 그려진다.
스트로크는 non-zero winding rule을 사용하는데 이는 경로 교차점이 채워진다는 것을 의미한다.




디시 위의 코드를 보자.

  1. 마우스가 움직일 때마다 해당 이벤트의 offsetX와 offsetY의 값을 받는다.
  2. 마우스가 클릭 상태가 아니라면 painting 값은 false이다.
  3. painting의 값이 false일 때, beginPath와 moveTo가 작동한다.
    이는 연필을 들고 계속 이동하는 것이며 경로는 초기화된다.
  4. 마우스를 클릭하면 painting 값은 true이다.
  5. painting의 값이 true일 때, lineTo와 stroke가 작동한다.
    클릭시 경로가 생기는데 첫 클릭의 시작점은 이전 moveTo의 좌표이며 도착점은 lineTo의 좌표이다. 해당 좌표를 그린다.(stroke())
  6. 클릭을 한 상태에서 계속 마우스를 이동하면, 시작점은 이동 직전의 lineTo의 좌표이며 도착점은 이동한 후의 좌표이다. 그리고 해당 좌표에 따라 그린다.(stroke())
  7. 그리기를 종료하면(마우스를 때면), painting의 값은 false가 되며 다시 경로는 초기화된다.

이 코드에는 문제점이 있다.(4월 12일)

아래 다른 방법으로 그림 그리기 코드 참고




다른 방법으로 그림 그리기

첫번째 방법과는 다르게 closePath가 추가 되었다.

위의 코드는 다음과 같다.

  1. 1번 방법과 마찬가지로 마우스가 이동할 때 offset 값을 받는다.
  2. painting의 값이 false 일땐 아무 일도 일어나지 않는다.
  3. painting의 값이 true 일때(마우스를 클릭했을 때) 먼저 경로가 초기화 된다.
    이후의 클릭한 지점으로 출발점이 옮겨진다.
  4. 클릭한 상태로 마우스가 이동하면(painting의 값이 계속 true)
    이동지점으로 도착점이 설정되고 출발점과 도착점을 그린다.
  5. 최초의 시작점은 클릭한 지점이지만 마우스 클릭 상태로 이동하면 시작점은 직전의 도착점이 된다.
  6. 마우스를 때면 painting의 값은 false로 바뀐다.
    이와 함께 마우스를 땐 지점과 그 직전의 지점을 연결한다.

closePath()

CanvasRenderingContext2D.closePath()

현재 하위 경로의 시작 부분과 연결된 직선을 추가한다.

shape이 이미 닫혔거나 점이 하나만 있으면 이 함수는 아무 작업도 수행하지 않는다.
이 메서드는 직접적으로 아무것도 그리지 않는다. stroke() 또는 fill() 메서드를 사용하여 그릴 수 있다.

추가 왜 이 코드를 써야하는가?(4월 12일에 추가)

그림 그리기 코드를 1번과 2번으로 작성하였지만 1번 코드에 문제점이 있다는 것을 알게 되었다.
1번 코드는 설명하자면, 마우스의 클릭상태에 따라 startPainting과 stopPainting으로 painting의 값을 변경해가며 그림을 그리는 상태를 조절하는 함수이다.

그리고 onMouseMove를 통해 그림을 그리게 되는데 이때 1번 코드는 onMouseMove 함수 안에 if(!paiting) 이라는 조건문을 걸어 그림이 중지(완성) 시키는데 이 부분이 문제이다.

즉, 마우스가 움직이는 중 if(!painting)이 되어야 한다는 것인데, onMouseMove로 그림을 그리다 마우스에 손을 떼고 다시 움직여야 if(!panting) 이후의 코드가 작동한다는 것이다. 만약 마우스를 움직이다 마우스에 손을 떼고 그 이후에 마우스를 움직이지 않으면 if(!paiting) 코드가 작동하지 않는다. 그러므로 1번 코드 보단 2번 코드를 사용해야한다.

기타 효과

캔버스 위로 그림을 그리는 것에 다양한 스타일을 지정할 수 있다.
즉, 그림 도구의 색이나 형태를 변경할 수 있다.

선의 색
strokeStyle = color

선의 굵기
lineWidth = value;

선의 끝모양
lineCap = type

이외에도 다양한 스타일을 적용할 수 있다.
https://developer.mozilla.org/ko/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors




참고

profile
생경하다.

0개의 댓글