Canvas 좌표와 HTML 좌표 사이의 간격

최종욱·2025년 4월 18일

Uuno

목록 보기
3/7
post-thumbnail

🎯 서론: 왜 이 문제가 중요한가?

Canvas 기반 편집기를 Konva로 개발하면서 가장 큰 기술적 난관 중 하나는 Canvas 요소와 HTML 요소 간 좌표 차이였다.
사용자가 보는 위치와 실제 Konva Canvas에서 인식하는 위치가 달라 툴바, 입력창 등의 HTML 오버레이가 엉뚱한 위치에 렌더링되는 문제가 반복적으로 발생했다.

이 글에서는 이 좌표 오차가 왜 발생하는지, 그리고 이를 어떻게 해결했는지에 대해 실제 경험을 바탕으로 정리한다.


⚙️ HTML과 Canvas 좌표계 차이의 본질

1. 서로 다른 좌표 단위

  • CanvasCanvas 픽셀 좌표계를, HTMLCSS 픽셀 좌표계를 기준으로 한다.

  • 브라우저의 확대/축소나 디바이스의 devicePixelRatio 설정에 따라 좌표 오차가 누적될 수 있다.

2. Konva의 scale 적용 방식

  • Konva의 scale은 Canvas 내부 transform으로 적용된다.

  • 반면 HTML은 CSS의 transform 속성으로 처리되므로, 서로 다른 좌표 변환 방식을 사용한다.

3. 부모 컨테이너의 padding, border, flex 영향

  • Konva <Stage>의 부모 요소에 padding, border, flex 설정이 있으면 좌표 보정이 반드시 필요하다.

4. 스크롤/뷰포트 기준 좌표차

  • getClientRect()는 DOM 기준, getAbsolutePosition()은 Konva 내부 기준이다.

  • 따라서 스크롤이 있는 페이지에서는 툴바 위치가 어긋나는 원인이 될 수 있다.


🧪 실제 문제 사례: 툴바 위치가 맞지 않던 이유

문제 상황

텍스트나 도형을 클릭하면 요소 하단 중앙에 툴바를 띄우는 구조를 구현 중이었다.
초기에는 getAbsolutePosition()으로 좌표를 받아 다음과 같이 툴바 위치를 설정했다:

const position = node.getAbsolutePosition();
setToolbar({ x: position.x, y: position.y });

그러나 실제 렌더링된 툴바는 완전히 다른 위치에 표시되었다.
이는 getAbsolutePosition()scale, 회전, 그룹화 변형을 반영하지 않는 논리 좌표만 반환하기 때문이다.


✅ 해결 방법 : getClientRect() 기반 좌표 계산

보다 정확한 위치 계산을 위해 getClientRect()를 사용한 코드로 교체했다:

const handleUpdateToolbarNode = (node: Konva.Node) => {
  requestAnimationFrame(() => {
    const rect = node.getClientRect();
    setToolbar({
      x: rect.x + rect.width / 2 - (TOOLBAR_WIDTH * zoom) / 2,
      y: rect.y + rect.height + 8,
    });
  });
};
  • getClientRect()는 실제 렌더링 기준의 x, y, width, height 정보를 포함한다.

  • 이를 기반으로 요소의 하단 중앙에 툴바가 정확히 위치하게 된다.


두 메서드의 비교

1. getAbsolutePosition()

반환값: { x, y } (논리 좌표)
장점: 변형 전 순수 좌표 확인 가능
단점: 스케일, 회전, 그룹 트랜스폼 미반영

2. getClientRect()

반환값: { x, y, width, height }
장점: 실제 렌더링된 bounding box 조회 가능
단점: CSS와 Canvas 스케일 차이에 대한 보정 필요


🔄 Zoom 적용 후 추가 문제와 해결

Zoom 기능을 추가한 이후, 좌표가 또다시 어긋나는 문제가 발생했다.
Konva 좌표는 확대된 상태로 계산되므로, HTML 요소는 Zoom을 나누어 보정해야 한다:

const textPosition = textNode.getAbsolutePosition();
const areaPosition = {
  x: textPosition.x / zoom,
  y: textPosition.y / zoom,
};
setupTextareaStyles({ textarea, textNode, areaPosition });

✨ 결론

HTML과 Canvas는 본질적으로 좌표계가 다르다.

이 차이를 정확히 이해하고, getClientRect()와 zoom 보정을 적절히 적용한 결과, 툴바 및 입력창 같은 HTML 오버레이 요소들이 사용자 기준 위치에 정확하게 렌더링되도록 구현할 수 있었다.

이 경험을 통해 좌표계 문제를 단순 보정이 아닌 시스템적 차이로 인식하고 대응하는 사고력을 키울 수 있었다.


에서

profile
항상 “Why?”로 시작하는 프론트엔드 개발자

0개의 댓글