\(@^0^@)/
[TIL] html5 canvas로 그림판을 만들어보자. 본문
우선 결과물을 먼저 보도록 하자.
시간 날 때마다 기능을 조금씩 더 추가해보고, 리팩터링 할 예정이다.
마크업
마크업을 동적 생성으로 구현하면 더 좋았을 수도 있겠지만, 우선 canvas를 알아가는 것이 목표이기에 간편하게 html단에서 마크업을 구현하였다.
JS에서 활용하기 위해 아래의 tool들에 알맞은 dataset을 주었다.
<button class="tool-btn">
<img src="./img/pencil.jpg" data-tool="pencil" />
</button>
<button class="tool-btn">
<img src="./img/paint.jpg" data-tool="paint" />
</button>
<button class="tool-btn">
<img src="./img/eraser.jpg" data-tool="eraser" />
</button>
예를 들어보면 연필의 경우 button안의 img태그에 data-tool="pencil"을 주었음.
이렇게 dataset을 사용하면, JS단에서 클릭이벤트를 활용하여 해당 이미지를 클릭할 경우 어떤 데이터인지 쉽게 알 수 있다.
스타일링
굵기를 선택하는 버튼의 경우에는 현재 div안에 button을 마크업 한 상태여서 button에 background 검정을 주고, height로 굵기를 구분하였음.
.liner-thin-btn {
width: 35px;
height: 1px;
background-color: black;
}
.liner-normal-btn {
width: 35px;
height: 10px;
margin-left: 15px;
background-color: black;
}
색상을 선택하는 버튼의 경우에는 dataset을 이용하여 스타일링을 적용해주었다. data-color가 검정일 경우, 배경을 검정으로. 동적으로 생성한다면 조금 더 간결하게 만들 수 있을 것 같기도 한데, 현재는 뚜렷하게 떠오르지가 않는 상태임.
.color-btn[data-color="black"] {
background-color: black;
}
.color-btn[data-color="gray"] {
background-color: gray;
}
구현
1. canvas 구현하기 위해 두줄의 코드를 무조건적으로 생성해주기.
const canvas = document.querySelector(".canvas");
const ctx = canvas.getContext("2d");
2. canvas에 그림을 그리기 위해 canvas에 mousedown, mousemove, mouseup 세 개의 이벤트를 생성.
- mousedown : 마우스 왼쪽 클릭을 할 경우 이벤트가 시작해야 하고 false였던 변수를 true로 준다.
- mousemove : 해당 변수가 true일 경우 마우스를 이리저리 움직이면 이벤트가 발생해야 하고
- mouseup : 마우스 왼쪽 클릭한 것을 떼면 mouseup 이벤트가 발생하면서 false가 되고, mousedown과 mousemove 이벤트가 닫혀야 함.
(function () {
const canvas = document.querySelector(".canvas");
const ctx = canvas.getContext("2d");
let drawingMode;
function startDrawHandler() {
drawingMode = true;
}
function finishDrawHandler() {
drawingMode = false;
}
function drawHandler(e) {
if (!drawingMode) return;
}
canvas.addEventListener("mousedown", startDrawHandler);
canvas.addEventListener("mousemove", drawHandler);
canvas.addEventListener("mouseup", finishDrawHandler);
})();
3. mousemove 이벤트를 구현해보자.
- 새로운 canvas 기능을 시작할 땐 beginPath()를 붙여주는 것이 좋음.
- true일 경우 (mouseup일 경우)에만 이벤트가 작동되며, 해당 canvas의 높이와 길이에 사이즈 10의 동그란 원이 작성된다.
- fill을 써주지 않는다면 그림이 그려지지 않는다. 그림을 그리기 위해서는 fill() 또는 stroke()를 적어주어야 하는데,
둘의 차이점은 fill은 점을 채우고, stroke는 윤곽선을 그린다.
function drawHandler(e) {
if (!drawingMode) return;
ctx.beginPath();
ctx.arc(e.offsetX, e.offsetY, 10, 0, Math.PI * 2, false);
ctx.fill();
}
4. tools 중 하나를 클릭하면 해당 기능 구현하기.
- 각 요소들을 select 해서
const tools = document.querySelector(".tools");
const colors = document.querySelector(".color-btns");
const liners = document.querySelector(".width-liners");
- 이벤트를 각각 생성해준다.
colors.addEventListener("click", setColor);
liners.addEventListener("click", setLiner);
tools.addEventListener("click", setTool);
- 각각의 데이터를 저장할 변수를 만들어주고, 알맞은 초기값을 넣어준다.
let color = "black";
let liner = 10;
let tool = "pencil";
- dataset을 이용하여 각각의 기능들을 넣어준다.
- color일 경우 점을 채우는 스타일 색상을 변경해주고
- eraser일 경우에는 canvas의 스타일 색상을 넣어주면, 지우개 기능을 할 수 있음.
- 굵기인 liner의 경우에는 drawHandler의 arc함수에서 사이즈를 설정하는 부분에 알맞은 변수의 값을 할당해준다.
function setColor(e) {
color = e.target.dataset.color;
ctx.fillStyle = color;
}
function setTool(e) {
tool = e.target.dataset.tool;
if (tool === "eraser") ctx.fillStyle = "#eee";
}
function setLiner(e) {
liner = e.target.dataset.liner;
}
function drawHandler(e) {
ctx.arc(e.offsetX, e.offsetY, liner, 0, Math.PI * 2, false);
}
생각날 때마다 기능들 추가하기
1. 저장(이미지 다운로드) 기능 추가
지난번 이미지 에디터 구현할 때 사용했던 저장 기능을 추가해보았다.
현재 그림판에 다른 컴포넌트 추가 없이 그림판만 사용하고 있지만, 나중에 다른 기능들을 추가할지도 모르니 우선 그림판 전체를 감싸는 main태그를 html단에서 생성하여, 구현하는 js 쪽에서 불러왔다.
const $container = document.querySelector("main");
다운로드 버튼을 클릭하면, 이벤트가 생성되게 핸들러를 생성해주고
$download.addEventListener("click", download);
- 지난번과 같이 사용자가 그림 그린 canvas을 url로 생성하여 url을 받고
- a태그를 이용해 href에 url을 넣고, 알맞은 파일명으로 저장한다.
- 가장 바깥의 태그에 append 한 후, a태그가 클릭되도록 한다.
- 그 후, 바로 a태그가 삭제되도록 하면 다운로드 기능이 만들어진다.
지난번에 구현했던 기능들을 추가하는 것이라서, 쉽게 구현하였다.
[ 출처 및 참고 : https://www.youtube.com/watch?v=ovf8cbKtBH0 ,
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D ]
'TIL' 카테고리의 다른 글
[TIL] react-router v5 VS. react-router v6 (0) | 2022.08.28 |
---|---|
[TIL] SWR를 사용하여 데이터를 가져와보자. (0) | 2022.08.05 |
[TIL] 제어 컴포넌트(Controlled Components)와 비제어 컴포넌트(Uncontrolled Components)의 특징 (0) | 2022.07.26 |
[TIL] 바닐라 자바스크립트로 글쓰기 에디터를 만들어보자. (2) | 2022.07.22 |
[TIL] 바닐라 자바스크립트로 이미지 에디터를 만들어보자. (0) | 2022.07.21 |