DOMRect와 getBoundingClientRect
이번에는 브라우저에게 “이 요소, 화면에서 어디에 있어요?”라고 물어보는 방법을 봅니다.
그 대답이 getBoundingClientRect()입니다. CSS 기반 에디터를 만들 때 이 함수는 꽤 자주 부르게 됩니다. selection outline을 맞추고, hover 대상을 찾고, viewport 안에 보이는지 확인하려면 결국 화면 기준 bounds가 필요합니다.
다만 주의할 점이 있습니다. 이 값은 모델 좌표가 아니라 브라우저가 이미 계산해준 viewport 기준 결과입니다. 친절하지만, 그대로 믿고 저장하면 곤란합니다.
DOMRect는 viewport 기준이다
요소에서 getBoundingClientRect()를 호출하면 left, top, width, height 같은 값이 나옵니다.
const rect = element.getBoundingClientRect();
이때 rect.left와 rect.top은 문서 전체 기준이 아니라 현재 viewport 기준입니다. 화면 왼쪽 위가 원점입니다.
rect = element.getBoundingClientRect()
localX = clientX - rect.left
pageX = clientX + scrollX
포인터의 clientX/clientY도 viewport 기준이므로, 둘을 빼면 요소 내부 local 좌표를 만들 수 있습니다.
localX = clientX - rect.left
localY = clientY - rect.top
아주 자주 쓰는 공식입니다. 너무 자주 나와서 나중에는 손이 먼저 씁니다.
transform된 요소의 rect는 AABB다
회전된 사각형을 생각해봅시다. 원래 width는 100, height는 60이어도 45도 회전하면 화면을 감싸는 직사각형은 더 커집니다.
getBoundingClientRect()는 회전된 모양 자체가 아니라, 그 모양을 화면 축에 맞춰 감싸는 axis-aligned bounding box를 반환합니다.
AABB = min/max(transformed corners)
그래서 회전된 요소의 DOMRect가 원래 width/height보다 커질 수 있습니다. 이걸 모르고 selection bounds를 맞추면 “왜 선택 박스가 커졌지?”라는 질문이 나옵니다. 답은 브라우저가 틀린 게 아니라, 우리가 어떤 bounds를 보고 있는지 구분하지 않은 겁니다.
저장값으로 쓰지는 말자
DOMRect는 빠른 관측값으로 좋습니다. 하지만 에디터의 원본 모델로 저장하기에는 애매합니다. 이미 scroll, layout, transform이 반영된 결과이기 때문입니다.
정확한 모델은 이런 값에서 다시 계산하는 편이 좋습니다.
x, y, width, height, localMatrix, worldMatrix
DOMRect는 화면에 보이는 결과를 확인하거나 빠른 broad phase에 쓰고, 진짜 편집 데이터는 모델에서 관리합니다.
데모에서 볼 것
데모에서는 회전된 요소의 DOMRect가 왜 원래 크기보다 커지는지 확인합니다.
오늘의 핵심은 getBoundingClientRect()가 아주 유용하지만, 어디까지나 viewport 기준 관측값이라는 점입니다. 친절한 조교처럼 써야지, 학적부 원본으로 쓰면 안 됩니다.