HTML5의 Canvas tag는 그래프를 그리거나 사진을 합성하거나, 애니메이션을 만드는 데에 사용하는 태그이다. Javascript를 활용하여 2D 그래픽을 그릴 수 있는데, 이번 시간에는 마우스를 따라 그림을 그리되 그 색상과 두께가 지속적으로 변하도록 만들어볼 것이다.

이전 실습들에 비해 코드량이 다소 많아 보일 수 있겠지만, Canvas위에서 움직이는 마우스의 동작에 따라 로직을 구성한다고 생각하면 크게 어렵지 않게 구현이 가능하다.

마우스가 사용하는 동작은 크게 네 가지이다.

누르기(mousedown), 떼기(mouseup), 이동(mousemove) 그리고 부가적으로 화면 밖으로 이동(mouseout)이다.

Canvas에 그림이 그려지는 순간은 마우스가 눌러졌을 때부터 뗄 때까지의 이동하는 경로이다. 물론 마우스가 화면 밖으로 나간다면 그림은 그려지지 않는다.

components/Canvas.js

Canvas component가 마운트되면 먼저 캔버스의 화면 크기를 전체 윈도우 크기에 맞춰주고, 몇 가지 필요한 초기 설정을 해준다.

먼저 getContext() function을 통해 CanvasRenderingContext2D context에 접근한다. 해당 컨텍스트는 렌더링과 그리기 함수를 사용할 수 있게 해준다.

이후에는 strokeStye(도형의 윤곽선 색상), lineJoin(두 선이 만나는 지점의 모양), lineCap(선의 끝 모양), lineWidth(선 굵기)를 설정해준다.

this.canvas = ReactDom.findDOMNode(this);    
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.ctx = this.canvas.getContext('2d');
this.ctx.strokeStyle = '#BADA55';
this.ctx.lineJoin = 'round';
this.ctx.lineCap = 'round';
this.ctx.lineWidth = 100;

위에서 언급되었던 마우스의 네 가지 동작에 따른 처리 로직을 구현해주고 eventListener에 등록해준다.

먼저 마우스가 클릭되고 그림을 그리기 시작하는 부분에 대해서는 startDrawing function으로 처리한다. 이 함수는 마우스가 클릭된 지점의 좌표를 넘겨주고, state 중 그림이 시작됨을 표현하는 flag인 isDrawing을 true로 업데이트 해준다(reducer에서 처리해준다).

가장 복잡한 mouseMove는 먼저 isDrawing이 false일 경우(마우스가 눌려있지 않거나 화면 밖을 나갔을 때)는 마우스를 아무리 움직여도 바로 종료된다.

mouseMove()를 통해서 선이 시작되는 지점인 lastX와 lastY state와 선의 색상인 hue를 갱신해준다.

새로운 path(여기서는 line)를 만들어주는 beginPath()를 해주고, moveTo와 lineTo를 통해 line의 시작과 끝을 정해준다. 마지막은 stroke()를 통해 캔버스에 그려주기. 그려진 라인의 색상은 미리 hue state에 의해서 정해진다.

if(!isDrawing) return;
mouseMove(e.offsetX, e.offsetY);
this.ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`; this.ctx.beginPath();
this.ctx.moveTo(lastX, lastY);
this.ctx.lineTo(e.offsetX, e.offsetY);
this.ctx.stroke();

라인의 두께가 한없이 굵어지거나 한없이 얇아지지 않도록 일정 범위 내에서 direction flag를 보고 오르내리도록 해주었다.

if(direction) {      
this.ctx.lineWidth++;
} else {
this.ctx.lineWidth--;
}
if(this.ctx.lineWidth === 101 || this.ctx.lineWidth === 10) {
toggleDirection();
}

mouseup과 mouseout은 isDrawing state를 false로 수정하도록 stopDrawing function을 걸어주었다.

Canvas component에 대부분의 로직이 구현되어 있어서 이 외에는 매우 간단하다.

//actions/index.jsexport const START_DRAWING = 'START_DRAWING';
export const STOP_DRAWING = 'STOP_DRAWING';
export const MOUSE_MOVE = 'MOUSE_MOVE';
export const TOGGLE_DIRECTION = 'TOGGL_DIRECTION';

export function stopDrawing() {
return {
type: STOP_DRAWING
}
}

export function startDrawing(lastX, lastY) {
return {
type: START_DRAWING,
lastX: lastX,
lastY: lastY
}
}


export function mouseMove(lastX, lastY) {
return {
type: MOUSE_MOVE,
lastX: lastX,
lastY: lastY
}
}

export function toggleDirection() {
return {
type: TOGGLE_DIRECTION
}
}

actions는 4가지로, canvas에서와 유사하게 그리기 시작과 끝, 그리는 중을 나타내주는 세 가지 액션에 추가적으로 선의 굵기를 줄일지 늘릴지를 전환해주는 TOGGLE_DIRECTION 타입이 있다.

// containers/CanvasContainer.jsimport { connect } from 'react-redux';

import Canvas from '../components/Canvas';
import { startDrawing, stopDrawing, mouseMove, toggleDirection } from "../actions";

const mapStateToProps = state => {
return state;
};

const mapDispatchToProps = dispatch => {
return {
startDrawing: () => {
dispatch(startDrawing());
},
stopDrawing: () => {
dispatch(stopDrawing());
},
mouseMove: () => {
dispatch(mouseMove());
},
toggleDirection: () => {
dispatch(toggleDirection());
}
}
};

const CanvasContainer = connect(mapStateToProps, mapDispatchToProps)(Canvas);

export default CanvasContainer;

CanvasContainer 역시 특별하지 않다. 관리되는 모든 state는 그대로 Canvas component에서 사용되기 때문에 별도의 처리를 해줄 필요가 없고, 단지 actions만 dispatch시켜주는 함수를 만들어 넘겨주었다.

reducer에서 각 action에 따른 새로운 state를 반환해준다.

  1. START_DRAWING은 선이 시작되는 좌표와 isDrawing(:true)를 갱신해주고
  2. STOP_DRAWING은 isDrawing을 false로 바꿔준다.
  3. MOUSE_MOVE는 색상코드를 주어진 범위 안에서 계속 증가하게 해주고, 최대값이 되면 다시 0부터 시작하도록 한다. 마우스가 이동하는 동안 계속해서 그림이 그려져야 하니 last좌표들도 계속 갱신해준다.
  4. TOGGLE_DIRECTION은 말 그대로 direction state를 토글해준다.

구현 결과는 다음과 같이

마우스 움직임으로 그림을 그릴 수 있는 캔버스가 완성되었다.

Github Source Code

woohyun