이번 글에서 다룰 내용
- 브라우저 창에 마우스 호버하면(mouseover) 슬로우모션으로 애니메이션 실행
- 마우스로 움직이는 공 잡아서 드래그
- mousedown, mousemove, mouseup, mouseout
mouseover
로 호버했을 때 공들의 속도를 늦추기mouseout
으로 마우스가 브라우저 밖으로 나갔을 때 속도 원상복귀//app.js
constructor(){
...
this.slowDown();
}
...
slowDown() {
this.canvas.addEventListener('mouseover', () => {
this.balls.forEach((ball) => {
ball.vx /= 20;
ball.vy /= 20;
});
});
this.canvas.addEventListener('mouseout', () => {
this.balls.forEach((ball) => {
ball.vx *= 20;
ball.vy *= 20;
});
});
}
- 마우스로 움직이는 공을 클릭
- 클릭한 공은 마우스가 움직이는 대로 드래그될 것
- 공을 놓는 순간 원래 움직이던 방향으로 이어서 이동
//ball.js
...
draw() {
this.interaction();
}
interaction() {
this.canvas = document.getElementById('canvas');
this.canvas.addEventListener('mousedown', this.onMouseDown);
this.canvas.addEventListener('mouseup', this.onMouseUp);
this.canvas.addEventListener('mouseout', this.onMouseOut);
}
onMouseDown = (e) => {
console.log("mouse down");
}
onMouseUp = (e) => {
console.log("mouse up");
}
onMouseOut = (e) => {
console.log("mouse out");
}
//ball.js
mousedown() {
this.offsetX = e.clientX - this.x;
this.offsetY = e.clientY - this.y;
if (
Math.abs(this.offsetX) <= this.radius &&
Math.abs(this.offsetY) <= this.radius
) {
this.canvas.addEventListener('mousemove', this.onMouseMove);
}
//ball.js
onMouseMove = (e) => {
this.x = e.clientX - this.offsetX;
this.y = e.clientY - this.offsetY;
this.vx = 0;
this.vy = 0;
};
//ball.js
constructor(stageWidth, stageHeight, radius, speedX, speedY, src) {
...
this.speedX = speedX;
this.speedY = speedY;
this.vx = this.speedX;
this.vy = this.speedY;
...
}
//ball.js
onMouseUp = (e) => {
this.vx = this.speedX / 20;
this.vy = this.speedY / 20;
this.canvas.removeEventListener('mousedown', this.onMouseDown);
this.canvas.removeEventListener('mousemove', this.onMouseMove);
};
🥊 따라서 mouseout 설정과 기존 방향을 기억하는 기능이 필요
//ball.js
onMouseOut = (e) => {
this.vx = this.speedX;
this.vy = this.speedY;
this.canvas.removeEventListener('mousedown', this.onMouseDown);
this.canvas.removeEventListener('mousemove', this.onMouseMove);
};
//ball.js
constructor() {
...
this.vx_minus = this.speedX < 0 ? -1 : 1;
this.vy_minus = this.speedY < 0 ? -1 : 1;
...
}
//ball.js
...
setMinus = () => {
this.vx_minus = this.vx < 0 ? -1 : 1;
this.vy_minus = this.vy < 0 ? -1 : 1;
};
//ball.js
onMouseDown = (e) => {
this.setMinus();
...
}
//ball.js
applyMinus = () => {
if (this.vx_minus * this.speedX < 0) {
this.vx *= -1;
}
if (this.vy_minus * this.speedY < 0) {
this.vy *= -1;
}
};
//ball.js
onMouseUp = (e) => {
//vx, vy 슬로모션 속도로 원상복귀
this.applyMinus();
//이벤트 제거
}
onMouseOut = (e) => {
//vx, vy 속도 원상복귀
this.applyMinus();
//이벤트 제거
}
//app.js
import Ball from './velogballs.js';
import {logos} from './consts.js';
class App {
constructor() {
this.canvas = document.createElement('canvas');
this.canvas.setAttribute('id', 'canvas');
this.ctx = this.canvas.getContext('2d');
document.body.appendChild(this.canvas);
window.addEventListener('resize', this.resize.bind(this), false);
this.resize();
this.balls = [];
this.createBall();
this.slowDown();
this.animate();
}
resize() {
this.stageWidth = document.body.clientWidth;
this.stageHeight = document.body.clientHeight;
this.canvas.width = this.stageWidth * 2;
this.canvas.height = this.stageHeight * 2;
this.ctx.scale(2, 2);
}
slowDown() {
this.canvas.addEventListener('mouseover', () => {
this.balls.forEach((ball) => {
ball.vx /= 20;
ball.vy /= 20;
});
});
this.canvas.addEventListener('mouseout', () => {
this.balls.forEach((ball) => {
ball.vx *= 20;
ball.vy *= 20;
});
});
}
createBall() {
for (let i = 0; i < logos.length; i++) {
let radius = Math.ceil(Math.random() * 30) + 10;
let speedX = Math.ceil(Math.random() * 50) + 5;
let speedY = Math.ceil(Math.random() * 50) + 5;
let signX = speedX % 2 === 0 ? -1 : 1;
let signY = speedY % 2 === 0 ? -1 : 1;
this.balls.push(
new Ball(
this.stageWidth,
this.stageHeight,
radius,
speedX * signX,
speedY * signY,
`./srcs/${logos[i]}.png`
)
);
}
}
animate() {
window.requestAnimationFrame(this.animate.bind(this));
this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight);
this.balls.forEach((ball) => {
ball.draw(this.ctx, this.stageWidth, this.stageHeight);
});
}
}
new App();
//ball.js
export default class Ball {
constructor(stageWidth, stageHeight, radius, speedX, speedY, src) {
this.img = new Image();
this.radius = radius;
this.diameter = this.radius * 2;
this.speedX = speedX;
this.speedY = speedY;
this.vx_minus = this.speedX < 0 ? -1 : 1;
this.vy_minus = this.speedY < 0 ? -1 : 1;
this.vx = this.speedX;
this.vy = this.speedY;
this.src = src;
this.x = this.radius + Math.random() * (stageWidth - this.diameter);
this.y = this.radius + Math.random() * (stageHeight - this.diameter);
}
draw(ctx, stageWidth, stageHeight) {
this.interaction();
this.x += this.vx;
this.y += this.vy;
this.bounceWindow(stageWidth, stageHeight);
ctx.save();
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.shadowColor = '#dee2e6';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 3;
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.clip();
ctx.drawImage(
this.img,
this.x - this.radius,
this.y - this.radius,
this.diameter,
this.diameter
);
this.img.src = this.src;
ctx.restore();
}
bounceWindow(stageWidth, stageHeight) {
if (this.x <= this.radius || this.x >= stageWidth - this.radius) {
this.vx *= -1;
this.x += this.vx;
}
if (this.y <= this.radius || this.y >= stageHeight - this.radius) {
this.vy *= -1;
this.y += this.vy;
}
}
interaction() {
this.canvas = document.getElementById('canvas');
this.canvas.addEventListener('mousedown', this.onMouseDown);
this.canvas.addEventListener('mouseup', this.onMouseUp);
this.canvas.addEventListener('mouseout', this.onMouseOut);
}
setMinus = () => {
this.vx_minus = this.vx < 0 ? -1 : 1;
this.vy_minus = this.vy < 0 ? -1 : 1;
};
applyMinus = () => {
if (this.vx_minus * this.speedX < 0) {
this.vx *= -1;
}
if (this.vy_minus * this.speedY < 0) {
this.vy *= -1;
}
};
onMouseDown = (e) => {
this.setMinus();
this.offsetX = e.clientX - this.x;
this.offsetY = e.clientY - this.y;
if (
Math.abs(this.offsetX) <= this.radius &&
Math.abs(this.offsetY) <= this.radius
) {
this.canvas.addEventListener('mousemove', this.onMouseMove);
}
};
onMouseMove = (e) => {
this.x = e.clientX - this.offsetX;
this.y = e.clientY - this.offsetY;
this.vx = 0;
this.vy = 0;
};
onMouseUp = (e) => {
this.vx = this.speedX / 20;
this.vy = this.speedY / 20;
this.applyMinus();
this.canvas.removeEventListener('mousedown', this.onMouseDown);
this.canvas.removeEventListener('mousemove', this.onMouseMove);
};
onMouseOut = (e) => {
this.vx = this.speedX;
this.vy = this.speedY;
this.applyMinus();
this.canvas.removeEventListener('mousedown', this.onMouseDown);
this.canvas.removeEventListener('mousemove', this.onMouseMove);
};
}
//consts.js
export const logos = [
'c',
'csharp',
'c++',
'java',
'js',
'mongoDB',
'MySQL',
'oracle3',
'php',
'PostgreSQL',
'python',
'r',
'SQLite',
'SQLServer',
'angular',
'backbone',
'django',
'ember',
'flask',
'laravel',
'node',
'preact',
'rails',
'react',
'spring',
'svelte',
'vue',
];
/*
stylesheet.css
*/
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
}
canvas {
width: 100%;
height: 100%;
}