캔버스 작업하면서 제일 헷갈리는 게 좌표계다.


세 가지 좌표

1. 스크린 좌표

마우스 이벤트에서 받는 좌표. event.clientX, event.clientY.

브라우저 창 기준이다.

2. 캔버스 좌표

Stage 내부 좌표. Shape의 x, y가 이거다.

줌/팬 하면 스크린 좌표랑 달라진다.

3. 실제 치수 (cm)

방 크기 300cm x 400cm 이런 거.

캔버스에서는 1cm = 1px로 쓰거나, 스케일 적용해서 쓰거나.


문제 상황

줌 200%에서 마우스 클릭했다.

// 마우스 위치
const screenX = event.clientX;  // 400

// 근데 캔버스에서 실제 위치는?
const canvasX = ???  // 200이어야 함

줌 2배니까 스크린 400 = 캔버스 200.


변환 공식

Konva가 변환 함수를 제공한다.

const stage = stageRef.value.getStage();
const pointer = stage.getPointerPosition();  // 스크린 좌표

// 캔버스 좌표로 변환
const transform = stage.getAbsoluteTransform().copy().invert();
const canvasPos = transform.point(pointer);

getAbsoluteTransform().invert()가 핵심.


유틸 함수로 빼기

매번 쓰기 귀찮으니까:

function screenToCanvas(stage: Konva.Stage, screenPos: { x: number, y: number }) {
  const transform = stage.getAbsoluteTransform().copy().invert();
  return transform.point(screenPos);
}

function canvasToScreen(stage: Konva.Stage, canvasPos: { x: number, y: number }) {
  const transform = stage.getAbsoluteTransform().copy();
  return transform.point(canvasPos);
}

cm 변환

실제 치수 표시할 때:

const SCALE = 1;  // 1px = 1cm

function pxToCm(px: number) {
  return px / SCALE;
}

function cmToPx(cm: number) {
  return cm * SCALE;
}

지금은 1:1이라 의미없지만, 나중에 스케일 바꿀 때 유용함.


삽질 포인트

처음에 event.offsetX 썼다가 삽질함.

// 이거 쓰면 안 됨
const x = event.offsetX;

// Stage의 container 기준이라 줌/팬 반영 안 됨

getPointerPosition() + 변환이 정답.


다음 글에서 무한 그리드 구현.

#4 - 무한 그리드