html canvas 다각형 hover event 구현

오준상·2021년 2월 5일
2
post-thumbnail

서론

html canvas로 여러가지 실험을 해보던 과정에서, html canvas에서 다각형을 그린 후에, 그 안에서 hover event를 받아야 하는 문제가 생겼다. google느님께 여쭤보니, 다 사각형이나 원형의 hover event만 다루고 있다는 것을 알게 되었다.
그래서 직접 구현한 과정과, 코드를 공유하려고 한다.

cavas에서 다각형 그리기

우선 다각형을 그리기 위해서는, 다각형 꼭짓점의 좌표가 필요하다.
우선 다각형 꼭짓점의 좌표를 다음과 같이 가지고 있다고 생각하겠다.

const coordinates = [x1,y1,x2,y2....];

다음과 같이 꼭짓점의 좌표를 가지고 있을 때, 다음과 같은 코드로 다각형을 그릴 수 있다.

function drawPolygon: function(
      context, // document.getElementById('canvas id').getContext('2d');
      cordinates, // 위에 있는 꼭짓점 좌표
      color, // 다각형 색깔
    ) {
        // 맨처음 펜을 옮기고 점을 찍는 코드입니다.
      this.initDraw(
        context,
        cordinates[0],
        cordinates[1],
        color
      );
        // 그 다음부터, 다음 좌표로 펜을 옮겨 투명 선을 긋습니다.
      for (let i = 0; i < cordinates.length; i = i + 2) {
        context.lineTo(
          cordinates[i],
          cordinates[i + 1]
        );
      }
      this.endDraw(context, isHover);
    }
function initDraw(context, firstX, firstY, color, fillColor) {
      context.beginPath();
      // 그리기를 시작하겠다.
      context.moveTo(firstX, firstY);
      // 펜을 처음 좌표로 옮긴다.
      context.strokeStyle = color;
      // 다각형의 색깔을 정한다,
      context.lineWidth = 2.5;
      // 다각형 선의 두께를 정한다. (px 단위)
    },
function endDraw(context){
  context.closePath();
  // path 닫기
  context.stroke();
  // 펜으로 그어 놓은 선을 canvas에 띄운다.
}

이렇게 하면 다음과 같은 다각형을 만들 수 있다.

다각형 hover event 구현하기

위와 같이 다각형을 그렸으면, 이제 hover event를 구현 해야 한다.
우리가 평소에 봤던 예제들은 거의 사각형같은 정해진 도형을 가지고 계산해야 했다.
하지만 다각형은 점도 여러개라서 기존에 쓰던, x2 - x1같은 공식을 쓸 수 없다.
그럼 어떻게 해야 할까?

기하와 벡터 부분에서 나오는 것을 사용하면 된다. (필자는 마이스터고등학생이라 모른다)
다음 링크를 참고했다. 이 링크에는 알고리즘도 제공하여서, 이 알고리즘에다가 canvas event를 끼얹었다
[기하] 다각형 내부 외부 판별 - bowbowbow

다음 링크를 보면, 다각형 내부에서, 오른쪽이나 왼쪽으로 반 직선을 그었을 경우에 다각형과 만나는 교점의 개수가 홀수 개라면 다각형 내부에 있는 점이라는 규칙을 이용했습니다.

strcut vector2{
int x, y;
}
/* STL vector를 이용해서 다각형 자료형을 정의 함 */
typedef vector<vector2> polygon;

bool isInside(vector2 B, const polygon& p){
//crosses는 점q와 오른쪽 반직선과 다각형과의 교점의 개수
int crosses = 0;
for(int i = 0 ; i < p.size() ; i++){
	int j = (i+1)%p.size();
	//점 B가 선분 (p[i], p[j])의 y좌표 사이에 있음
	if((p[i].y > B.y) != (p[j].y > B.y) ){
		//atX는 점 B를 지나는 수평선과 선분 (p[i], p[j])의 교점
		double atX = (p[j].x- p[i].x)*(B.y-p[i].y)/(p[j].y-p[i].y)+p[i].x;
		//atX가 오른쪽 반직선과의 교점이 맞으면 교점의 개수를 증가시킨다.
	if(B.x < atX)
		crosses++;
	}
}
return crosses % 2 > 0;
}

출처: https://bowbowbow.tistory.com/24 [멍멍멍]

이제 canvas 코드를 확인해 보자.

function isHover(mouseCordinate, coordinate) {
      let crosses = 0;
      for (let i = 0; i < coordinate.length; i = i + 2) {
        const j = (i + 2) % coordinate.length;
        if (
          coordinate[i + 1] > mouseCordinate.y !=
          coordinate[j + 1] > mouseCordinate.y
        ) {
          let atX =
            ((coordinate[j] - coordinate[i]) *
              (mouseCordinate.y - coordinate[i + 1])) /
              (coordinate[j + 1] - coordinate[i + 1]) +
            coordinate[i];
          if (mouseCordinate.x < atX) crosses++;
        }
      }
      return crosses % 2 > 0;
};

function mouseHoverHandler(event, context, coordinates) {
      this.clearCanvas(context);
      // canvas reset하는 함수
      // canvas에 hover animation이 발생하면, 
      const isHover = this.isHover(
        {
          x: event.offsetX,
          y: event.offsetY,
        },
        coordinates
      )
      this.drawPolygon(
        context,
        coordinates,
        tagColor[index],
        fillColor[index],
        isHover
      );
};

function clearCanvas(context) {
      context.beginPath();
      //canvasSize는 canvas의 사이즈를 의미. reset 할때 씁니다.
      const scale = this.canvasSize.width / this.imageHtml.width;
      context.clearRect(
        this.imageHtml,
        0,
        0,
        this.canvasSize.width,
        this.imageHtml.height * scale
      );
};

function drawCloth(
      context,
      coodinates,
      color,
      fillColor,
      isHover
    ) {
      this.initDraw(
        context,
        coodinates[0],
        coodinates[1],
        color,
        fillColor
      );
      for (let i = 0; i < coodinates.length; i = i + 2) {
        context.lineTo(
          coodinates[i],
          coodinates[i + 1]
        );
      }
      this.endDraw(context, isHover);
}

결론

생각보다 기하나 벡터 그리고 미분 적분이 programming에 사용되는 경우가 많다.

profile
만들고싶은걸만듭니다

0개의 댓글