💙 들어가며
canvas에 그린 그림들에 여러가지 동적 움직임을 줘보자!
✏️ 학습내용 정리
#canvas.on
on했을 때 실행할 이벤트들이 모여있다. (ondrag, onclick 등)
~~할 때 실행할 이벤트들이 ondrag와 같은 이름으로 모여있다.
마우스이벤트인 onclick함수를 이용해 클릭했을 때 콘솔에 clicked가 출력되도록 해보자.
💡 다음을 출력하면?
/**@type{HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
canvas.onclick = function () {
console.log("clicked");
};
var ctx = canvas.getContext("2d");
var ball = new Path2D();
ball.arc(100, 100, 30, 0, Math.PI * 2);
ctx.fill(ball);
⭕ 출력결과:
도형을 그린 것이 canvas에 출력되고,
화면을 클릭할 때마다 콘솔에 clicked가 출력된다.

#clientX, clientY
click event를 활용하면 클릭을 하는 곳의 좌표를 직접 확인할 수도 있다.
그러려면 먼저 아래의 차이를 구분할 줄 알아야겠다.
💡 내가 보는 화면에서 X, Y 좌표의 차이
원점이 어디인지를 생각하면서
windowX, windowY / screenX, screenY / clientX, clientY를 구분해보자.
캔바스 화면을 클릭했을 때 좌표를 콘솔에 출력할 것이기 때문에
clientX와 clientY를 이용해볼 수 있겠다.
콘솔에 출력하기 위해서는 onclick을 했을 때 event가 발생했음을 인지하고
그 event의 x좌표와 y좌표를 활용할 수 있어야 하기 때문에
onclick fucntion에 e(event라는 뜻)라는 매개변수를 넣어주어야 한다.
💡 클릭하는 곳의 좌표를 출력해보자.
canvas.onclick = function (e) //매개변수 e
{
console.log("clicked");
console.log(e.x, e.y);
}
⭕ 출력결과:
클릭하는 곳의 좌표가 다음과 같이 찍히고 있다!

#(번외)e.shiftkey
마우스 이벤트가 발생할 때 shiftkey를 누르면
boolean값 true를 반환하는 속성인 e.shiftkey를 활용해서
shift키를 누른 상태로 클릭하면 사각형이 출력되고
그냥 클릭만 하면 원이 출력되도록 해보자.
✅ 왜 나는 정확한 좌표에 동그라미나 네모가 찍히지 않을까?
html에서 기본 style을 제거하지 않으면
body의 margin이나 혹은 body에 글씨가 있는 것을 인식해서
좌표가 정확하게 맞지 않을 수 있으니 주의할 것!
코드를 확인해보자.
💡 e.shiftkey를 사용해보자!
/**@type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var ctx = canvas.getContext("2d");
canvas.onclick = function (e) {
// console.log("clicked");
// console.log(e.x, e.y);
console.log(e.shiftKey);
if (e.shiftKey) {
var square = new Path2D(/*"M e.x e.y h 30 v30 h -30 v -30"*/);
square.rect(e.x, e.y, 30, 30);
ctx.stroke(square);
}
else {
var circle = new Path2D();
circle.arc(e.x, e.y, 30, 0, Math.PI * 2);
ctx.stroke(circle);
}
};
⭕ 출력결과:
shift키를 누른 상태로 클릭할 때 e.shiftkey의 상태를 출력하면 true가 나온다.
이를 조건문에 활용하여 true일 때는 사각형이 그려지게끔 한다.

#클릭하면 원 그리기
클릭하면 원 그리고 다른 좌표를 클릭하면 새로운 좌표에만 원이 생기게 하기
💡 다음을 출력하면?
canvas.onclick = function (e) {
console.log("clicked");
console.log(e.x, e.y);
var ball2 = new Path2D();
ball2.arc(e.x, e.y, 30, 0, Math.PI * 2);
ctx.clearRect(0, 0, 900, 700);
ctx.fill(ball2);
}
⭕ 출력결과:
기존에 클릭했던 원은 사라지고 새로 클릭한 좌표에만 원이 출력된다.
왜냐하면 다음 원을 클릭하면 화면 전체를 clearRect가 지우고 난 뒤에
새로운 원이 생기기 때문이다.

#마지막에 클릭한 위치의 값을 남겼으면 좋겠다.
이번에는 새로고침을 해도 마지막에 클릭한 위치에 원이 생겼으면 좋겠다.
어떻게 하는 것이 좋을까?
새로고침을 해도 이전 좌표를 기억할 수 있게 저장을 해놓아야 한다.
이때 사용할 수 있는 많은 방법 중에
localStorage함수를 사용해보자!
💡 마지막에 클릭한 위치를 기억하게 해보자.
var x = localStorage.getItem("x") || 100;
var y = localStorage.getItem("y") || 100;
/**@type{HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var ctx = canvas.getContext("2d");
canvas.onclick = function (e) {
x = e.x;
y = e.y;
localStorage.setItem('x', x);
localStorage.setItem('y', y);
var ball = new Path2D();
ball.arc(x, y, 30, 0, Math.PI * 2);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(ball);
}
// console.log(x);
var ball = new Path2D();
ball.arc(x, y, 30, 0, Math.PI * 2);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(ball);
⭕ 출력결과:
새로고침을 해도 값을 기억하려면 다른 곳에 값을 저장해두어야 한다.
이때 사용할 수 있는 방법 중에 하나가 locaStorage라는 함수이다.
이 함수를 사용하면 마지막 값을 별도로 저장해두고 있어서 우리가 편하게 꺼내서 쓸 수 있다.
사용하는 방법은 간단하다. getter/setter와 똑같다.
클릭할 때 좌표를 localStorage.setItem("변수명", 기억시킬 값)에 저장한다.
※ 기억시킬 값: event가 일어날 때 x좌표(e.x)와 y좌표(e.y)
그리고 이벤트 영역 바깥에 x와 y의 값을 저장할 수 있는 전역변수를 하나 만들고
localStorage.getItem("값")을 대입해준다.
(그리고 그 값이 null이면 기본값을 넣을 수 있도록 falsy || 초기화값을 활용한다.)
※ 대입해주는 방식을 보면 알겠지만 더블쿼터(" " ) 안에 값이 들어가기 때문에 String으로 저장된다.
마지막으로 마우스 이벤트 영역 바깥에
getItem을 이용해서 얻어낸 x좌표와 y좌표를 이용해서 원을 그려주면 끝!

#setTimeout 일회성 알람이다.
특정시간에 원하는 함수가 1회 나오도록 하는 함수인데,
구조가 특이하다. setTimeout안에 인자로 호출하고 싶은 함수와 시간간격이 들어간다.
window.setTimeout(function(){}, 3000);
함수를 넣고 ms의 단위로 시간을 넣는다. (3000이면 3초)
💡 3초 뒤에 1회 나타나게 해보자.
window.setTimeout(function () {
var ctx = canvas.getContext("2d");
var ball = new Path2D();
ball.arc(x, y, 30, 0, Math.PI * 2);
ctx.clearRect(0, 0, 900, 700);
ctx.fill(ball);
}, 3000)
#setInterval 주기적 알람이다.
setTimeout과 다르게 계속해서
특정시간마다 원하는 함수가 반복적으로 나오도록 한다.
(인자는 setTimeout과 동일하다!)
💡 계속해서 움직이게 해보자.
window.setInterval(function () {
x++; //프레임을 쪼개서 서서히 움직이게 한다.
var ctx = canvas.getContext("2d");
var ball = new Path2D();
ball.arc(x, y, 30, 0, Math.PI * 2);
ctx.clearRect(0, 0, 900, 700);
ctx.fill(ball);
}, 50);
그러면 최종적으로 움직이는 원을 그려보면 다음과 같이 할 수 있다.
💡 클릭하는 좌표값을 기억했다가 천천히 이동해보자.
var x = localStorage.getItem("x") || 100; //String으로 저장
var y = localStorage.getItem("y") || 100; //String으로 저장
/**@type{HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var ctx = canvas.getContext("2d");
canvas.onclick = function (e) {
x = e.x;
y = e.y;
localStorage.setItem('x', x);
localStorage.setItem('y', y);
var ball = new Path2D();
ball.arc(x, y, 30, 0, Math.PI * 2);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(ball);
}
//초기값이 있기 때문에 별도로 클릭하지 않아도
//새로고침하자마자 ball이 생성되고 움직인다.
window.setInterval(function () {
x++;
var ctx = canvas.getContext("2d");
var ball = new Path2D();
ball.arc(x, y, 30, 0, Math.PI * 2);
ctx.clearRect(0, 0, 900, 700);
ctx.fill(ball);
}, 50);
#애니메이션 이동: 여러 번에 나누어서 이동하기
여태까지는 이미지를 그리면 한 번에 빡! 출력되는 방식이었다.
그리고 x나 y값을 ++이나 --하는 것 말고는
내가 원하는 방식(클릭하는 곳으로 등..)으로 이동을 할 수가 없었다.
(좌표를 계속해서 새로 업데이트 할 수 없었기 때문..!)
애니메이션처럼 자연스럽게 원하는 좌표로 이동하려면 어떻게 해야할까?
애니메이션은 정적인 그림에 조금씩 변화를 주면서 계속해서 출력하는 방식으로
마치 움직이는 듯한 느낌을 주는 것이다. (EX) 패트와 매트
그렇기 때문에 우리가 원하는 좌표로 애니메이션처럼 계속해서 조금씩 움직일 수 있으려면
단위를 잘게 쪼개서 정해진 시간만큼 계속해서 출력을 시켜야 한다.
(setInterval 함수 재등장!)
여기서 단위를 작게 쪼갤수록 빠르게 움직이고 크게 쪼갤수록 느리게 움직인다.
※ 빠르게 움직인다는 것은 하드의 성능이 받쳐줘야 한다. (초당 프레임수가 걸려있기 때문!)
#동일한 시간내에 다른 속도? 동일한 속도로 다른시간?
거리를 움직여야 할 때 고려해야 할 것은 시간과 속도이다.
거리가 고정된 상황에서 시간이 고정되이라면 거리에 따라서 속도가 달라질 것이다.
반대로 동일한 속도로 움직이게 속도를 고정시킨다면 거리에 따라서 움직이는 시간이 달라질 것이다.
무슨말일까? 아래 이미지를 확인해보자.
그러면 어떻게 움직이게 하는게 좋을까?
거리에 따라 시간이 좀 더 걸리더라도 일정한 속도로 움직이는 것이
우리가 일반적으로 생각하는 방식일 것이다.
그렇다면 일정한 속도는 어떻게 구해야 할까?
일정한 거리를 움직일 수 있도록 1을 가기 위한 단위 너비와 높이를 구해야 한다.
단위 너비와 높이를 구하는 공식은 Math.sqrt()를 사용하며
구체적으로는 아래와 같다.
똑같은 내용을 코드로 작성해보자.
💡 계속 움직이기 위해 벡터x와 벡터y 좌표를 구하자
var dx = localStorage.getItem("x");
var dy = localStorage.getItem("y");
var w = dx - x;
var h = dy - y;
var d = Math.sqrt(w * w + h * h);
var vx = w / d;
var vy = h / d;
#클릭할 때 원하는 방향으로 움직이게 해보자
일단 w, h, d를 이용해서 벡터x(vx)와 벡터y(vy)를 구했으니
애니메이션을 통해 원하는 방향으로 움직이게 할 수 있는 기준이 생겼다.
그러면 어떻게 적용할 수 있을까?
클릭한 위치까지 움직였다면 멈추는 애니메이션을 만들어보자.
그러면 일단 클릭한 위치에서 멈추는 조건문을 걸어야 하는데,
클릭한 위치의 좌표(dx, dy)와
현재 위치의 좌표(x, y)를 비교하는 연산을 하게 될 경우 주의할 것이 있다.
💡 자바스크립트는 기본이 실수형이다. 정수와 정수를 나누어도 실수가 나온다.
때문에 소수점이 잘리면서 미세하게 목적지가 벗어나기 때문에,
정확한 목적지로 갈 수가 없다. 범위값으로 비교해야 한다.
그렇다면 정확한 값으로 비교하는 것이 아니라
범위값을 가지고 비교해야겠다.
범위값을 가지고 비교를 하되, 더이상 값이 변하지 않도록 조건을 걸어보자.
💡 어떻게 하면 값을 고정할 수 있을까?
window.setInterval(function () {
//좌표가 해당 범위안에 들어왔을 때 vx와 vy를 0으로 고정시킨다.
if (
(x - 1 < dx && dx < x + 1) &&
(y - 1 < dy && dy < y + 1)
) {
vx = 0;
vy = 0;
}
//vx와 vy가 0으로 고정되면 더이상 값이 더해지지 않고,
x += vx;
y += vy;
var ctx = canvas.getContext("2d");
var ball = new Path2D();
//x와 y값이 변하지 않고 고정된다. (왜? 계속 0이 더해지는거니까!)
ball.arc(x, y, 30, 0, Math.PI * 2);
ctx.clearRect(0, 0, 900, 700);
ctx.fill(ball);
}, 50);
💙 마치며
1.
html에서 script를 <head>에 작성하기 때문에
<body>에 설정한 <canvas>를 읽지 못하는 경우가 있을 수 있다.
defer를 쓰는 것을 잊지 말아야겠다.
💡 잊지말아야 할 점!
script를 맨 마지막에 실행하도록 defer를 꼭 넣어줘야 한다.
<script src="game.js" defer></script>
2.
마우스 이벤트 등을 잘 활용하면
작은 게임을 만들기 수월할 것 같다..!
화이팅!
'JAVASCRIPT' 카테고리의 다른 글
[뉴렉처 6기] JavaScript│ES6 입문1│let│const│백틱│객체뽀개기│다형성(변수키값)(230728) (0) | 2023.07.30 |
---|---|
[뉴렉처 6기] JavaScript│prototype을 활용한 캡슐화│배열│객체마다 다른 움직임 주기(230726) (0) | 2023.07.30 |
[뉴렉처 6기] JavaScript│canvas 플랫폼│Path│다양한 도형과 선 그려보기(230721) (0) | 2023.07.24 |
[뉴렉처 6기] JavaScript│객체지향2│자료형식│prototype│shadowing(230720) (0) | 2023.07.24 |
[뉴렉처 6기] JavaScript│객체지향1│생성자│오버로드│캡슐화│prototype(230718) (0) | 2023.07.18 |