이번에는 캔버스 인터랙션 - 1에 움직이는 사각형을 누르면 패널이 나타나게 처리하고자 한다.
총 3가지의 실습으로 이루어져있다.
실습하기 앞서서 기존에 있던 Box 클래스를 별도의 Box.js 파일을 생성해서 만들어주었다.
class Box {
constructor(index, x, y, speed, width, height) {
this.index = index;
this.x = x;
this.y = y;
this.speed = speed;
this.width = width;
this.height = height;
this.color = "rgba(0,0,0,0.5)";
this.draw();
}
draw() {
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.width, this.height);
context.fillStyle = "#fff";
context.fillText(this.index, this.x, this.y + 30);
}
}
사각형 클릭시에 패널을 그릴 Panel.js 파일에 Panel 클래스을 생성해주었다.
class Panel {
constructor() {
this.scale = 0;
this.angle = 0;
}
draw() {
context.fillStyle = "rgba(255, 240, 0, 0.8)";
// 트랜스 폼을 사용하기 이전에 리셋
context.resetTransform();
// 중심점을 바꾼뒤
context.translate(oX, oY);
context.scale(this.scale, this.scale);
// 이동시킬께 아니기 때문에 중심점을 원위치시킴
context.translate(-oX, -oY);
context.fillRect(oX - 150, oY - 150, 300, 300);
context.resetTransform();
}
showContent() {
context.fillStyle = "#000000";
if (selectedBox) {
context.fillText(selectedBox.index, oX, oY);
}
}
}
여기에서 translate와 scale을 사용한 이유는 사각형을 클릭했을때 패널이 점점 커지면서 표시되도록 구현하기 위함이다.
만약 바로 표시하고 싶으면 resetTransForm, scale, trnalsate 메서드 부분을 모두 삭제해주고, html 코드의 scale을 증가시켜주는 부분을 삭제하면 된다.
<!DOCTYPE html>
<html>
<!-- 생략 -->
<body>
<h1>Interaction</h1>
<canvas class="canvas" width="600" height="400"></canvas>
<script src="./Box.js"></script>
<script src="./Panel.js"></script>
<script>
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
const boxes = [];
const mousePosition = { x: 0, y: 0 };
let selectedBox; // 클릭된 박스를 넣어놓을 변수
let panel;
let oX;
let oY;
let step; // 애플리케이션의 상태(단계)를 저장 1 ~ 4
context.font = "bold 30px sans-serif";
const render = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
let box;
// 뒤의 박스를 그려주는 역할
for (let i = 0; i < boxes.length; i++) {
box = boxes[i];
box.draw();
}
switch (step) {
case 1:
// 박스의 위치를 이동하는 역할
for (let i = 0; i < boxes.length; i++) {
box = boxes[i];
box.x += box.speed;
if (box.x > canvas.width) {
box.x = -box.x;
}
}
break;
case 2:
panel.scale += 0.02;
panel.draw();
if (panel.scale >= 1) {
step = 3;
}
break;
case 3:
panel.draw();
panel.showContent();
break;
}
requestAnimationFrame(render);
};
let tempX, tempY, tempSpeed;
const init = () => {
step = 1;
oX = canvas.width / 2;
oY = canvas.height / 2;
for (let i = 0; i < 10; i++) {
// 박스가 캔버스 영역을 벗어나서 그려짐을 방하하기 위해 0.8을 곱해줌
tempX = Math.random() * canvas.width * 0.8;
tempY = Math.random() * canvas.height * 0.8;
// 1 ~ 2 사이의 값
tempSpeed = Math.random() * 2 + 1;
// 크기가 최소 30x30이고 100x100 이내의 랜덤한 상자크기를 만들고싶어
tempWidth = Math.random() * 70 + 30;
tempHeight = Math.random() * 70 + 30;
boxes.push(
new Box(i, tempX, tempY, tempSpeed, tempWidth, tempHeight)
);
}
panel = new Panel();
render();
};
const clickHandler = (e) => {
mousePosition.x = e.offsetX;
mousePosition.y = e.offsetY;
let box;
for (let i = 0; i < boxes.length; i++) {
box = boxes[i];
if (
mousePosition.x >= box.x &&
mousePosition.x <= box.x + box.width &&
mousePosition.y >= box.y &&
mousePosition.y <= box.y + box.height
) {
selectedBox = box;
}
}
// 어떤 박스가 선택되었는지 확인
if (step === 1 && selectedBox) {
step = 2;
} else if (step === 3) {
step = 1;
selectedBox = null;
panel.scale = 0;
}
};
canvas.addEventListener("click", clickHandler);
init();
</script>
</body>
</html>
결과는 다음과 같다 슥~
패널이 나타날때 빠른 속도로 표시되고 점점 천천히 그려지도록 가속도를 부여하여 패널을 표시해보았다.
가속도를 구하는 공식은 다음과 같다.
가속도 : 현재크기 = 현재크기 + (목표크기 - 현재크기) * 0.1
이 공식을 그림으로 보자면 아래와 같이 설명할 수 있다.
쉽게 생각하면 목표지점으로 우리가 이동할때 첫번째 발에 많이가고 두번째 발에 첫번째보다 덜 가고, 세번째 발에 두번째 발보다 덜 가고 이런식으로 반복함을 의미한다.
공식을 이해했으니 이제 코드를 적용해보자!
코드는 render 함수의 switch문의 case 2만 변경해주면 된다.
const render = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
let box;
// 뒤의 박스를 그려주는 역할
for (let i = 0; i < boxes.length; i++) {
box = boxes[i];
box.draw();
}
switch (step) {
case 1:
// 박스의 위치를 이동하는 역할
for (let i = 0; i < boxes.length; i++) {
box = boxes[i];
box.x += box.speed;
if (box.x > canvas.width) {
box.x = -box.x;
}
}
break;
case 2:
// 현재크기 = 현재크기 + (목표크기 - 현재크기) * 0.1
panel.scale = panel.scale + (1 - panel.scale) * 0.05;
// panel.scale += 0.02;
panel.draw();
console.log(panel.scale);
if (panel.scale >= 0.999) {
panel.scale = 1;
step = 3;
}
break;
case 3:
panel.draw();
panel.showContent();
break;
}
requestAnimationFrame(render);
};
결과는 다음과 같다 슈우욱! 뿅~
가속도의 값이 1이 되었을때 회전도 마무리가 되어야 하기 때문에 현재크기 * 720(2바퀴)을 이용해서 값을 처리해준다.
<script>
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
const boxes = [];
const mousePosition = { x: 0, y: 0 };
let selectedBox; // 클릭된 박스를 넣어놓을 변수
let panel;
let oX;
let oY;
let step; // 애플리케이션의 상태(단계)를 저장 1 ~ 4
let renderID;
context.font = "bold 30px sans-serif";
const render = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
let box;
// 뒤의 박스를 그려주는 역할
for (let i = 0; i < boxes.length; i++) {
box = boxes[i];
box.draw();
}
switch (step) {
case 1:
// 박스의 위치를 이동하는 역할
for (let i = 0; i < boxes.length; i++) {
box = boxes[i];
box.x += box.speed;
if (box.x > canvas.width) {
box.x = -box.x;
}
}
break;
case 2:
// 현재크기 = 현재크기 + (목표크기 - 현재크기) * 0.1
panel.scale = panel.scale + (1 - panel.scale) * 0.05;
// 스케일 (0~1) * 720
panel.angle = panel.scale * 720;
panel.draw();
if (panel.scale >= 0.999) {
panel.scale = 1;
panel.angle = 720;
step = 3;
}
break;
case 3:
// 여기에서 cancelAnimationFrame을 하면 이전 renderID를 캔슬하기 때문에 정상동작하지 않음.
panel.draw();
break;
}
renderID = requestAnimationFrame(render);
if (step === 3) {
panel.showContent();
cancelAnimationFrame(renderID);
}
};
let tempX, tempY, tempSpeed;
const init = () => {
step = 1;
oX = canvas.width / 2;
oY = canvas.height / 2;
for (let i = 0; i < 10; i++) {
// 박스가 캔버스 영역을 벗어나서 그려짐을 방하하기 위해 0.8을 곱해줌
tempX = Math.random() * canvas.width * 0.8;
tempY = Math.random() * canvas.height * 0.8;
// 1 ~ 2 사이의 값
tempSpeed = Math.random() * 2 + 1;
// 크기가 최소 30x30이고 100x100 이내의 랜덤한 상자크기를 만들고싶어
tempWidth = Math.random() * 70 + 30;
tempHeight = Math.random() * 70 + 30;
boxes.push(
new Box(i, tempX, tempY, tempSpeed, tempWidth, tempHeight)
);
}
panel = new Panel();
render();
};
const clickHandler = (e) => {
mousePosition.x = e.offsetX;
mousePosition.y = e.offsetY;
let box;
for (let i = 0; i < boxes.length; i++) {
box = boxes[i];
if (
mousePosition.x >= box.x &&
mousePosition.x <= box.x + box.width &&
mousePosition.y >= box.y &&
mousePosition.y <= box.y + box.height
) {
selectedBox = box;
}
}
// 어떤 박스가 선택되었는지 확인
if (step === 1 && selectedBox) {
step = 2;
} else if (step === 3) {
step = 1;
selectedBox = null;
panel.scale = 0;
render();
}
};
canvas.addEventListener("click", clickHandler);
init();
</script>
결과는 다음과 같다 휘리릭 뿅!