수학은 개발을 위한 필요조건일까요?
수학은 개발을 위한 필요조건일까?
개발을 시작하려는 사람들은 "저 수포자인데 개발자 할 수 있을까요?"라는 질문을 종종 합니다. 대부분 사람들의 대답은 "수학 몰라도 괜찮아요. 프론트엔드는 더더욱요." 입니다.
저는 전공이 수학인 프론트엔드 개발자입니다. 그래서 오히려 "수학 잘하면 개발에 도움 되나요?", "코테 잘 보겠네요?" 같은 질문을 자주 받습니다.
실제로 수학은 없어도 개발을 시작하는 데 큰 문제는 없습니다. 하지만, 더 정교하고 복잡한 문제를 마주했을 때는 이야기가 조금 달라집니다.
에디터 개발중 DOM API만으로는 해결이 어려운 문제를 마주했고, 고등학교 수학을 기반으로 문제를 해결한 경험이 있습니다.
문제 상황
전 글에서 말씀드렸듯이, 제가 참여했던 에디터 제품에서는 커서를 직접 구현하는 방식을 사용했습니다. 커서를 직접 그려주려면, 위치, 너비, 높이, 깜빡임 주기 등 모든 속성을 수동으로 계산해 구현해야 합니다.
구현된 커서 로직은 큰 문제 없이 잘 작동되고 있었습니다. 그런데 어느 날, 도형을 회전 시키는 기능이 추가되면서 예상치 못한 이슈가 발생했습니다.
도형이 회전되면 그 안의 텍스트도 함께 회전됩니다. 하지만 커서는 회전을 따라가지 못했고, 완전히 엉뚱한 위치에 그려지는 문제가 발생했습니다.
처음에는 이렇게 생각했습니다.
"도형도 transform: rotate로 회전 금방 되는데, 커서도 비슷하게 하면 되겠지?"
하지만 그건 아주 큰 착각이었습니다...
원인 파악
커서를 렌더링하는 로직은 DOM 요소의 위치와 크기를 구할 때 사용하는 getBoundingClientRect() 메서드를 활용하고 있었습니다.
이 메서드로 텍스트 요소의 좌표와 크기를 가져와 커서의 위치, 너비, 높이 등을 계산해서 커서를 그리고 있었죠.
문제는 getBoundingClientRect()에서 발생하였습니다.
getBoundingClientRect()는 회전된 요소의 "실제 크기"를 반환하지 않습니다.
대신, 회전된 요소를 포함할 수 있는 외접 사각형(bounding box)을 반환합니다.

즉, 회전 전 원본 요소의 크기가 아닌, 회전 이후 그 요소를 감쌀 수 있는 가장 작은 직사각형(외접 사각형)의 크기를 반환하고 있었던거죠.
회전된 도형 내부의 텍스트는 도형을 따라 잘 회전되었지만,커서는 별도로 계산해 그려줘야 하기 때문에, 회전된 텍스트의 외접 사각형 값을 기준으로 위치를 계산하고 있었습니다. 그 결과, 커서가 전혀 다른 위치에 표시되는 문제가 발생했습니다.
기존의 좌표 계산 로직으로는 문제를 해결할 수 없었습니다.
결국 커서를 도형과 같이 회전시켜서 그려주려면 회전된 상태에서의 텍스트 요소의 좌표, 너비, 높이를 찾아해내야 했습니다.
내가 알 수 있는 건...
아래는 제가 알 수 있는 정보입니다. 이 정보를 바탕으로, 회전된 상태에서도 정확한 위치에 커서를 그리는 작업이 필요했습니다.
- 회전된 텍스트 요소의 외접 사각형 좌표, 너비, 높이
- 회전 각도 (θ)
- 브라우저가 제공하는 커서의 외접 사각형 좌표, 너비, 높이
이 정보를 이용해, 회전된 상태에서도 커서가 정확한 위치에 그려지도록 해야했습니다.
- **삼각함수(sin, cos)**로 회전 전후 좌표 변환
- 점과 직선 사이 거리 공식으로 텍스트 내부 상대좌표 계산
-
회전 전 텍스트 요소의 실제 너비, 높이 구하기
-
회전된 상태에서 텍스트의 좌상단, 좌하단 꼭짓점 좌표 구하기
-
브라우저 커서로 실제 커서가 위치할 좌표 보정하기
-
직선 방정식과 거리 공식을 이용해 커서의 상대 위치 구하기 (2)에서 얻은 좌측 경계선과 커서 좌표 간 거리 계산 → 커서가 텍스트 내부의 어느 위치에 있어야 할지 계산
해결 과정
1. 회전 전 텍스트 요소의 실제 너비, 높이 구하기
회전된 텍스트 요소의 외접 **사각형(W, H)**과 **회전 각도(θ)**를 이용해,
회전되기 전 텍스트(=원본 텍스트)의 실제 너비와 높이를 삼각함수로 역으로 계산합니다.

function 원본텍스트요소구하기(rotate, 외접사각형, cos, sin) {
const W = 외접사각형.width; // 회전된 텍스트 요소의 외접사각형 width
const H = 외접사각형.height; // 회전된 텍스트 요소의 외접사각형 height
// w: 원본의 width, h: 원본의 height
// W = w * |cos(θ)| + h * |sin(θ)| (외접사각형의 가로)
// H = w * |sin(θ)| + h * |cos(θ)| (외접사각형의 세로)
// 연립방정식 풀이:
// W = w * cos + h * sin ... (1)
// H = w * sin + h * cos ... (2)
//
// (1) + (2): W + H = (w + h) * (cos + sin)
// (1) - (2): W - H = (w - h) * (cos - sin)
//
// 따라서:
// w + h = (W + H) / (cos + sin)
// w - h = (W - H) / (cos - sin)
//
// 최종적으로:
// w = [(W + H) / (cos + sin) + (W - H) / (cos - sin)] / 2
// h = [(W + H) / (cos + sin) - (W - H) / (cos - sin)] / 2
// 각 사분면 별로 cos, sin의 부호를 고려한 계산
if (rotate < 90) {
// 0° ≤ θ < 90°: cos > 0, sin > 0
const w = ((W + H) / (cos + sin) + (W - H) / (cos - sin)) / 2;
const h = ((W + H) / (cos + sin) - (W - H) / (cos - sin)) / 2;
return { width: w, height: h };
}
if (rotate < 180) {
}
if (rotate < 270) {
}
if (rotate < 360) {
}
}
원본 텍스트의 **크기(w, h)**를 삼각함수와 연립방정식을 통해 역으로 계산해 구했습니다.
2. 회전된 상태에서 텍스트의 좌상단, 좌하단 꼭짓점 좌표 구하기
앞서 구한 원본 텍스트의 너비/높이(w, h), 외접 사각형 좌표(X, Y), **회전 각도(θ)**를 바탕으로 삼각함수를 활용해 회전된 텍스트의 좌상단 또는 좌하단 꼭짓점 좌표를 계산합니다.
function 좌상단꼭짓점구하기(rotate, 원본텍스트요소, cos, sin, 외접사각형) {
const X = 외접사각형.left;
const Y = 외접사각형.top;
const W = 외접사각형.width;
const H = 외접사각형.height;
const w = 원본텍스트요소.width;
const h = 원본텍스트요소.height;
if (rotate < 90) {
return { x: X + h * sin, y: Y };
}
// 다른 4분면 처리
}

rotate < 90일 때는 외접 사각형 기준 왼쪽으로 h * sin(θ)만큼 이동한 위치가 좌상단 꼭짓점이 됩니다.
3. 브라우저 커서로 실제 커서가 위치할 좌표 보정하기
기존 브라우저 커서도 회전된 상태의 외접 사각형 형태로 제공되므로, 이 값을 바탕으로 삼각함수를 이용해 커서의 Top 위치를 보정해야 합니다.
function 회전된커서Top구하기(rotate, 브라우저커서, cos, sin) {
const X = 브라우저커서.left;
const Y = 브라우저커서.top;
const W = 브라우저커서.width;
const H = 브라우저커서.height;
// 1. 회전된 상태에서 브라우저 커서 기준점(Bottom) 좌표 구하기
let 회전커서Bottom;
if (rotate < 90) {
회전커서Bottom = { x: X, y: Y + H };
} else {
// 다른 4분면
}
// 2. 브라우저 커서 높이만큼 이동해서 Top 좌표 계산
let 회전커서Top;
if (rotate < 90) {
회전커서Top = {
x: 회전커서Bottom.x + H * sin,
y: 회전커서Bottom.y - H * cos
};
} else {
// 다른 4분면
}
return 회전커서Top;
}
4. 직선 방정식과 거리 공식을 이용해 커서의 상대 위치 구하기
회전된 텍스트 요소 안에서 커서를 정확히 그리려면, 커서의 절대 좌표를 기준으로 텍스트 내부에서의 상대 위치를 계산해야 합니다.
이를 위해, 회전된 텍스트 요소의 왼쪽 경계선을 직선으로 보고 커서가 그 직선으로부터 얼마나 떨어져 있는지를 점과 직선 사이 거리 공식으로 계산합니다.
function 커서상대위치계산(rotate, 커서Top좌표, 좌상단꼭짓점, 좌하단꼭짓점) {
// 좌상단꼭짓점, 좌하단꼭짓점: 회전된 도형의 왼쪽 경계선을 이루는 두 점
// 커서Top좌표: 회전된 커서의 Top위치
// 1단계: 두 점으로 직선의 방정식 구하기
// 직선의 일반형: ax + by + c = 0
const a = 좌하단꼭짓점.y - 좌상단꼭짓점.y; // y2 - y1
const b = 좌상단꼭짓점.x - 좌하단꼭짓점.x; // x1 - x2 (주의: 순서 바뀜)
const c = 좌하단꼭짓점.x * 좌상단꼭짓점.y - 좌상단꼭짓점.x * 좌하단꼭짓점.y;
// 2단계: 점과 직선 사이의 거리 공식 적용
// 거리 = |ax₀ + by₀ + c| / √(a² + b²)
const 분자 = Math.abs(a * 커서Top좌표.x + b * 커서Top좌표.y + c);
const 분모 = Math.sqrt(a * a + b * b);
const distance = 분자 / 분모;
if (rotate < 90) {
return distance
}
// 다른 사분면
}이렇게 구한 distance 값을 텍스트 요소 기준으로 적용하면, 커서가 내부에서 정확한 위치에 그려지도록 계산할 수 있습니다.
90°, 180°, 270°, 360° 일때는?
로직을 보면 90°, 180°, 270°, 360°와 같은 특정 각도일 때는 별도의 삼각함수 계산을 하지 않습니다. 이는 다음과 같은 이유 때문입니다:
- tangent 특이값 문제: 90도와 270도에서는
tan(θ)가 무한대(∞)가 되어 일반적인 삼각함수 기반 계산이 불가능합니다. - 기울기(slope) 계산 불가: 180도와 360도는 회전 방향이 수평이기 때문에 두 점으로 만든 직선의 기울기 계산에서 분모가 0이 됩니다. (
a = 0이 되어-b / a계산 불가) - 정확한 외접 사각형 반환: 해당 각도에서는
getBoundingClientRect()를 통해 회전된 외접 사각형을 그대로 얻을 수 있기 때문에, 복잡한 수학 없이도 위치를 보정할 수 있습니다.
따라서, 이러한 경우에는 각도별 특성을 고려하여 직접 보정하는 방식으로 처리합니다:
if (rotate === 90) {
// 90도 회전: x,y 좌표 교환 + 방향 조정
return { x: 커서Top좌표.y - 외접사각형.top, y: 외접사각형.right - 커서Top좌표.x };
}개발에 수학이 필요할까?
고등학교에서 배운 수학 지식으로 커서 위치 이슈를 해결할 수 있었습니다.
DOM API만으로는 넘을 수 없는 브라우저의 한계를 마주하며, 결국 직접 계산해야 해결할 수 있는 문제도 있다는 걸 절실히 느꼈습니다.
물론 "수포자도 개발자 할 수 있어요"라는 말이 틀린 건 아닙니다. 실제로 대부분의 개발 업무는 수학 없이도 충분히 가능하니까요.
하지만 더 정교하고 완성도 높은 개발을 원한다면, 복잡한 UI 문제나 성능 최적화, 알고리즘 구현을 마주했을 때 수학적 사고는 분명 강력한 무기가 됩니다.
수학은 개발에 반드시 필요 조건은 아니지만, 더 나은 결과를 만드는 충분 조건이 될 수 있다는 걸 이번 경험을 통해 배웠습니다.