https://velog.io/@kimbyungchan/canvas-animation
이전글을 참고하시면 기본적인 구조에대해 더 쉽게 이해하실수있습니다.
// Vector.ts
export default class Vector {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
distance(other: Vector): number {
return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
}
}
기존 Vector class에 두 점의 길이를 구하는 distance함수를 추가해줍니다.
//index.ts
import Shape from './Shape';
import Vector from './Vector';
import Circle from './Circle';
export default class App {
static instance: App;
width: number = window.innerWidth;
height: number = window.innerHeight;
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
delta: number = 0;
startTime: number;
frameRequestHandle: number;
shapes: Array<Shape> = [];
mousePosition: Vector = new Vector(0, 0);
constructor() {
App.instance = this;
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
this.canvas.addEventListener('mousemove', this.onMouseMove);
this.context = this.canvas.getContext('2d')!;
this.startTime = Date.now();
this.frameRequestHandle = window.requestAnimationFrame(this.frameRequest);
const column = 10;
const row = 10;
const columnWidth = this.width / column;
const columnHeight = this.width / column;
for (let y = 0; y < row; y++) {
for (let x = 0; x < column; x++) {
const position = new Vector(columnWidth * x + columnWidth * 0.5, columnHeight * y + columnHeight * 0.5);
this.shapes.push(new Circle(position));
}
}
document.body.appendChild(this.canvas);
}
onMouseMove = (e: MouseEvent) => {
this.mousePosition = new Vector(e.clientX, e.clientY);
}
frameRequest = () => {
this.frameRequestHandle = window.requestAnimationFrame(this.frameRequest);
const currentTime = Date.now();
this.delta = (currentTime - this.startTime) * 0.001;
this.startTime = currentTime;
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let i = 0; i < this.shapes.length; i++) {
this.shapes[i].update(this.delta);
this.shapes[i].render(this.context);
}
}
}
window.addEventListener('load', () => {
new App();
});
기본적으로 싱글톤 패턴에 사용할 instance 변수와, Circle을 일정한 간격으로 추가해주었습니다.
이제 마우스와 원의 거리에 따라 크기가 달라지게 변경해봅시다.
import Shape from './Shape';
import Vector from './Vector';
import App from './index';
const PI2 = Math.PI * 2;
export default class Circle extends Shape {
radius: number;
color: string;
constructor(position: Vector) {
super(position);
this.radius = 10;
this.color = 'rgba(0, 0, 0)';
}
update(delta: number) {
const distance = Math.max(App.instance.mousePosition.distance(this.position), 10);
this.radius = 1000 / distance;
}
render(context: CanvasRenderingContext2D) {
context.beginPath();
context.fillStyle = this.color;
context.arc(this.position.x, this.position.y, this.radius, 0, PI2);
context.fill();
}
}
싱글톤패턴으로 App에 아까 추가한 mousePosition을 불러와 길이를 구하고
Circle의 radius를 업데이트 해주었습니다.
마우스와 거리가 가까워 질수록 크기가 커지는데 약간 부자연스럽게 뚝뚝 끊기는 부분을 다듬어 봅시다.
// Circle.ts
import Shape from './Shape';
import Vector from './Vector';
import App from './index';
const PI2 = Math.PI * 2;
export default class Circle extends Shape {
endRadius: number;
startRadius: number;
color: string;
constructor(position: Vector) {
super(position);
this.startRadius = 10;
this.endRadius = 10;
this.color = 'rgba(0, 0, 0)';
}
update(delta: number) {
const distance = Math.max(App.instance.mousePosition.distance(this.position), 10);
this.endRadius = 1000 / distance;
this.startRadius = this.startRadius - (this.startRadius - this.endRadius) * 0.1;
}
render(context: CanvasRenderingContext2D) {
context.beginPath();
context.fillStyle = this.color;
context.arc(this.position.x, this.position.y, this.startRadius, 0, PI2);
context.fill();
}
}
radius변수가 삭제되고 startRadius에서 endRadius로 천천히 변하게 변경하였습니다.
넵 여기까지 캔버스를 이용한 간단한 마우스 인터랙션 이였습니다.
github: https://github.com/KimByungChan/velog-canvas-mouse-interaction