Canvas Mouse Interaction

김병찬·2020년 11월 11일
11

canvas

목록 보기
2/3
post-thumbnail
post-custom-banner

🥴 캔버스를 활용해 마우스를 이용한 간단한 인터랙션을 구현해봅시다.

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

profile
👀 시각적인 요소를 중요하게 생각합니다.
post-custom-banner

0개의 댓글