[JavaScript 30 Days Challenge] Fun with HTML5 Canvas

yuza🍊·2021년 10월 21일
0
post-thumbnail

Day 8-Fun with HTML5 Canvas

CODE

구현 사항: html canvas와 다양한 메소드들을 사용하여 그림 그리기

1) canvas를 선택하여 변수 canvas에 할당

const canvas = document.querySelector("#draw");

2) canvas에 무엇인가를 표시하려면 렌더링 컨텍스트에 접근해야 함 -> 변수 cts에 getContext() 사용하여 그리기 메소드와 속성을 가진 context를 할당하며, 메소드의 인자로 "2d"를 명시

const canvas = document.querySelector("#draw");
const ctx = canvas.getContext("2d");

3) window.innerHeight와 window.innerWidth(window의 viewport의 높이와 너비)를 canvas의 높이와 너비로 할당

canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

4) ctx의 strokeStyle (그림 그릴 때의 색상), lineJoin (선이 꺾이는 부분의 style), lineCap (선의 끝 부분의 style), lineWidth (선의 너비)를 설정

ctx.strokeStyle = "green";
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.lineWidth = 100;

5) draw() 함수 선언, mousemove 이벤트 발생 시 실행 -> 마우스가 브라우저 내에서 움직일 시 함수 실행

function draw(e) {}
canvas.addEventListener("mousemove", draw);

6) ctx.beginPath() 메소드를 통해 새로운 경로를 생성 -> moveTo() 메소드는 펜을 x와 y로 지정된 좌표로 옮김

let lastX = 0;
let lastY = 0;

function draw(e) {
	ctx.beginPath();
	ctx.moveTo(lastX, lastY);
}
  • 매개변수로 들어갈 lastX와 lastY의 값을 0으로 초기화하여 함수 바깥에 선언한 뒤 매개변수로 삽입

7) lineTo() 메소드는 현재의 드로잉 위치에서 x와 y로 지정된 위치까지 선을 그리도록 함

function draw(e) {
	ctx.beginPath();
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
}
  • 그 x와 y는 event.offsetX와 event.offsetY가 되어야 함
    • event.offsetX와 event.offsetY는 좌표를 출력하도록 하는 이벤트가 걸려있는 Dom Node를 기준으로 좌표를 표시함
    • 즉, canvas의 왼쪽 상단을 (0, 0)으로 하며 그 지점을 기준으로 마우스의 현재 위치 좌표를 얻을 수 있는 것

8) stroke() 메소드 사용하여 윤곽선 그리기

function draw(e) {
	ctx.beginPath();
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
}

9) 현재 마우스의 위치로 lastX와 lastY를 변경해야 쭉 이어서 그림을 그릴 수 있음 -> lastX와 lastY에 e.offsetX, e.offsetY를 구조 분해 할당

function draw(e) {
	ctx.beginPath();
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
}

10) 마우스를 클릭한 태로 마우스를 움직여야 그려지고, 마우스를 떼거나 마우스가 canvas를 벗어나면 그리는 것을 멈춰야 함 & 마우스를 클릭한 순간의 위치로부터 그림이 시작되어야 함

let lastX = 0;
let lastY = 0;
let isDrawing = false;

function draw(e) {
	if (!isDrawing) return;
	ctx.beginPath();
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
}

canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mousedown", (e) => {
        isDrawing = true;
        [lastX, lastY] = [e.offsetX, e.offsetY];
});
  • 현재 그림을 그리고 있는 상태인지를 표시하기 위한 변수 isDrawing 선언 후 false를 기본값으로 할당
    • mousedown 이벤트 발생 시 isDrawing을 true로 변경하며, lastX와 lastY를 mousedown 이벤트가 일어난 위치의 offsetX와 offsetY로 변경
    • 함수 실행 시 가장 먼저 isDrawing을 확인 -> false일 경우 바로 return하여 함수가 더 이상 실행되지 않도록 함

11) 마우스를 떼거나 마우스가 canvas 바깥으로 나가면 isDrawing을 false로 변경

let lastX = 0;
let lastY = 0;
let isDrawing = false;

function draw(e) {
	ctx.beginPath();
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
}

canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mousedown", (e) => {
        isDrawing = true;
        [lastX, lastY] = [e.offsetX, e.offsetY];
});
canvas.addEventListener("mouseup", () => (isDrawing = false));
canvas.addEventListener("mouseout", () => (isDrawing = false));

12) mousemove가 일어날 때마다 색상이 변하도록 하기 위해 변수 hue 선언 후 strokeStyle을 hsl(${hue}, 100%, 50%)로 설정

function draw(e) {
	ctx.beginPath();
	ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
}

13) hue 변수의 범위는 0부터 360까지 -> 함수가 실행될 때마다 hue를 1씩 증가시키며, 만약 hue가 360 이상이 되면 hue를 0으로 되돌리기

function draw(e) {
	ctx.beginPath();
	ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
}
hue++;
if (hue >= 360) {
	hue = 0;
}

14) lineWidth를 줄어들었다가 늘어나는 것을 반복하도록 만들기 위해 direction 변수 선언 -> direction이 true일 때 lineWidth를 증가시키며, false일 때 감소시킬 것, 또한 lineWidth가 100 이상으로 증가라거나 1 이하로 감소하는 시점에 direction의 T/F를 바꿈

function draw(e) {
	ctx.beginPath();
	ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
}
hue++;
if (hue >= 360) {
	hue = 0;
}

if (direction) {
	ctx.lineWidth++;
} else {
	ctx.lineWidth--;
}

if (ctx.lineWidth >= 100) {
	direction = false;
} else if (ctx.lineWidth <= 1) {
	direction = true;
}

내가 추가한 기능: 키보드 클릭 시 draw / erase 모드 변경, draw 모드 시 그림 그리고, erase 모드 시 흰 색으로 그릴 수 있도록 하여 지우개 효과 간접 구현

1) 모드를 저장할 변수 mode 선언 후 기본값으로 true 할당 -> keydown 이벤트 발생 시 mode를 변경하도록 함

<canvas tabindex="1" id="draw" width="800" height="800"></canvas>

(...생략...)


let mode = true;

function draw(e) {
	ctx.beginPath();
	ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
	ctx.moveTo(lastX, lastY);
	ctx.lineTo(e.offsetX, e.offsetY);
    ctx.stroke();
    [lastX, lastY] = [e.offsetX, e.offsetY];
}

(...생략...)

canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseup", () => (isDrawing = false));
canvas.addEventListener("mouseout", () => (isDrawing = false));
canvas.addEventListener("keydown", () => (mode = !mode));
  • 이 때, canvas에 keydown 이벤트를 listen 시키기 위해 tabindex="1" 속성을 부여해야 함 (참고)

2) 함수 기능을 분리하기 위해 customDrawing() 함수 선언 -> 매개변수로 event 객체, strokeStyle, lineWidth를 받아서 말 그대로 이 함수를 사용하여 그리기 기능을 커스텀할 수 있도록 함

function customDrawing(e, strokeStyle, lineWidth) {
        ctx.beginPath();
        ctx.strokeStyle = strokeStyle;
        ctx.lineWidth = lineWidth;
        ctx.moveTo(lastX, lastY);
        ctx.lineTo(e.offsetX, e.offsetY);
        ctx.stroke();
        [lastX, lastY] = [e.offsetX, e.offsetY];
}

3) draw() 함수 내에 customDrawing() 함수 삽입하여 이전과 같이 그림을 그릴 수 있도록 함 -> 이 때, mode를 확인하여 mode가 true일 땐 이전과 같이 그림 그려지게, false일 경우 lineWidth가 50이며 white 색상으로 그림이 그려지도록 하여 지우개 기능을 간접적으로 구현

function draw(e) {
	if (!isDrawing) return;
	if (!mode) {
		customDrawing(e, "white", 50);
	} else {
		customDrawing(e, `hsl(${hue}, 100%, 50%)`, ctx.lineWidth);
		hue++;
		if (hue >= 360) {
                hue = 0;
                }
              if (direction) {
                ctx.lineWidth++;
              } else {
                ctx.lineWidth--;
              }
              if (ctx.lineWidth >= 100) {
                direction = false;
              } else if (ctx.lineWidth <= 1) {
                direction = true;
       	      }
        }
}
profile
say hi to the world

0개의 댓글