
'펼쳐보기'와 '접기'를 오가는 토글 버튼 오른쪽에는 흔히 작은 삼각형 화살표를 둔다.
접혀 있을 때는 ▼, 펼쳐지면 ▲. 상태가 바뀔 때 화살표에 transform: rotate(180deg) 한 줄만
더하면 되니 구현도 간단하다.
그런데 이렇게 만든 화살표를 눌러보면 묘하게 어색하다. 제자리에서 방향만 바뀌는 게 아니라, 뒤집힐 때마다 위아래로 살짝 들썩인다. 고작 몇 픽셀 차이라 대수롭지 않게 넘기기 쉽지만, 한번 눈에 들어오면 누를 때마다 신경이 쓰인다. 원인은 회전 축과 삼각형의 무게중심이 서로 다른 곳에 있기 때문이다.
이런 어긋남은 흔히 "화살표를 1px 만 내려주세요" 같은 눈대중 보정으로 처리된다. 하지만 눈대중으로 맞춘 값은 크기가 바뀌면 다시 맞춰야 하고, 왜 그 값인지 설명할 수도 없다. 이 글에서는 어긋남의 정체인 무게중심을 이해하고, 감이 아니라 계산으로 바로잡는 방법을 살펴본다.
직접 눌러보기
먼저 차이를 직접 확인해 보자. 왼쪽은 회전 축을 기본값 그대로 둔 화살표이고, 오른쪽은 회전 축을 삼각형의 무게중심으로 옮긴 화살표다. 번갈아 눌러보면 왼쪽 화살표만 위아래로 오르내리는 것을 볼 수 있다.
차이가 잘 보이지 않는다면 가이드 보기를 켜보자. 붉은 점이 삼각형의 무게중심, 점선 가로줄이 접혀 있을 때의 무게중심 높이다. 왼쪽은 펼칠 때마다 점이 줄에서 벗어나고, 오른쪽은 줄 위에 그대로 머문다. 느리게와 크게를 함께 켜면 왼쪽 화살표가 반원을 그리며 이동하는 모습까지 볼 수 있다.
transform-origin: 50% 50%;transform-origin: 50% 66.67%;왜 출렁이는가
박스는 사각형, 무게는 아래로 쏠린다
CSS 로 삼각형을 그리는 가장 흔한 방법은 크기가 0인 요소에 border 를 두르고,
한 변만 색을 칠하는 것이다.
.arrow {
width: 0;
height: 0;
border-inline: 6px solid transparent;
border-bottom: 10px solid currentColor;
}
이 요소의 박스는 가로 12px, 세로 10px 의 사각형이지만 실제로 색이 칠해진 부분은 ▲ 모양이다. 꼭짓점 하나는 위에, 밑변은 아래에 있으니 칠해진 면적의 대부분이 박스 아래쪽에 몰려 있다. 박스의 정중앙과 우리 눈이 느끼는 도형의 중심이 처음부터 다른 셈이다.
rotate 는 박스 중심을 기준으로 돈다
transform 의 회전 축을 정하는 transform-origin 의 기본값은 50% 50%, 즉 박스의 정중앙이다.
박스 중심을 축으로 180도 돌리면 박스 안의 모든 점이 중심을 기준으로 정반대 위치로 옮겨진다.
위에서 66.67% 지점에 있던 무게중심은 위에서 33.33% 지점으로 이동한다.
결국 화살표를 뒤집을 때마다 무게중심이 높이의 1/3 만큼 위아래로 오가게 된다. 높이 10px 짜리 화살표라면 약 3.3px 이다. 숫자로는 작지만, 버튼 글자 옆에 붙은 작은 도형이 누를 때마다 3px 넘게 오르내리면 눈에 띌 수밖에 없다.
눈은 박스가 아니라 무게중심을 본다
박스의 중심은 회전 전후로 그대로인데, 왜 우리는 도형이 움직였다고 느낄까? 채워진 도형의 위치를 판단할 때, 눈은 보이지 않는 바깥 박스가 아니라 면적이 모여 있는 곳을 기준으로 삼기 때문이다.
아이콘 디자인의 시각 보정도 같은 현상을 다룬다. 원 안에 재생 버튼 ▶ 을 넣을 때 박스 기준으로 정확히 가운데 두면 왼쪽으로 치우쳐 보여서, 삼각형을 오른쪽으로 살짝 옮기는 식이다. 문제는 이 '살짝'을 대개 눈대중으로 정한다는 점이다. 보는 사람과 화면에 따라 값이 달라지고, 아이콘 크기가 바뀌면 처음부터 다시 맞춰야 한다.
하지만 눈이 기준으로 삼는 지점이 무게중심이라는 것을 알면 이야기가 달라진다. '얼마나 옮겨야 그럴듯해 보이는가'를 감으로 찾는 대신, 무게중심을 기준점에 맞추면 된다는 명확한 규칙이 생긴다. 그 규칙을 쓰려면 먼저 무게중심이 정확히 어디에 있는지 알아야 한다.
왜 66.67% 인가
▲ 의 무게중심이 위에서부터 높이의 2/3, 즉 66.67% 지점에 있다는 건 삼각형 무게중심의 성질에서 나온다. 중학교 수학에서 배우는 내용이지만, 막상 왜 그런지 떠올리려면 쉽지 않다. 한 단계씩 따라가 보자.
무게중심이란
무게중심은 물체의 무게가 한 점에 모여 있다고 볼 수 있는 점이다. 두께가 고른 판지를 삼각형으로 오려 손가락 끝 하나로 받친다고 해보자. 판지가 어느 쪽으로도 기울지 않고 균형을 잡는 점이 딱 하나 있는데, 그 점이 바로 무게중심이다. 앞서 말한, 우리 눈이 '도형이 놓인 위치'로 느끼는 곳도 이 점이다.
삼각형에서 이 점을 찾으려면 중선 하나만 알면 된다. 중선은 한 꼭짓점과 그 맞은편 변의 중점을 잇는 선분이다. 꼭짓점이 셋이니 중선도 세 개다.
1단계: 무게중심은 중선 위에 있다
삼각형 ABC 를 밑변 와 평행하게, 아주 얇은 띠로 잘게 썰었다고 생각해 보자. 띠가 충분히 얇으면 하나하나를 가느다란 막대로 볼 수 있고, 막대의 균형점은 정가운데다.
그런데 꼭짓점 A 에서 의 중점 D 로 내려온 중선 는 모든 띠의 정가운데를 지난다. 띠는 위로 갈수록 짧아지지만 왼쪽 끝과 오른쪽 끝이 A 를 향해 같은 비율로 줄어들기 때문에, 띠의 가운데 점도 A 와 D 를 잇는 직선에서 벗어나지 않는다.
모든 띠가 중선 위에서 균형을 잡으니, 띠를 모두 합친 삼각형 전체도 위 어딘가에서 균형을 잡는다. B 와 C 에서 내린 중선에 대해서도 똑같이 말할 수 있다. 결국 무게중심은 세 중선 위에 동시에 있어야 하므로, 세 중선이 만나는 한 점이 된다. 이 점을 보통 G 라고 부른다.
2단계: 무게중심은 중선을 2 : 1 로 나눈다
세 중선이 만나는 점 G 는 각 중선을 꼭짓점 쪽에서부터 2 : 1 로 나눈다. 이 성질은 두 가지 사실만 있으면 확인할 수 있다.
- 삼각형 중점 연결 정리: 삼각형 두 변의 중점을 이은 선분은 나머지 한 변과 평행하고, 길이는 그 변의 절반이다. D 는 의 중점, E 는 의 중점이니 는 와 평행하고 길이는 의 절반이다.
- 닮음: 중선 와 가 만나는 점을 G 라고 하자. 와 가 평행하므로 엇각이 같다(∠GAB = ∠GDE, ∠GBA = ∠GED). 두 각이 같으니 △GAB 와 △GDE 는 닮은 삼각형이고, 닮음비는 : = 2 : 1 이다.
닮은 삼각형은 대응하는 변의 비가 모두 같으므로 : = 2 : 1, : = 2 : 1 이 된다. 세 번째 중선 도 같은 방법으로 를 2 : 1 로 나누는 점을 지나니, 세 중선은 모두 같은 점 G 에서 만난다.
3단계: 무게중심 공식
2 : 1 이라는 성질을 좌표로 옮기면 무게중심 공식이 나온다. 세 꼭짓점을 A(x1, y1), B(x2, y2), C(x3, y3) 라고 하자.
- D 는 의 중점이므로, D 의 좌표는 B 와 C 좌표의 평균이다.
- G 는 A 에서 D 쪽으로, 길이의 2/3 만큼 간 점이다.
가로 좌표만 따로 계산해 보면 다음과 같다.
Dx = (x2 + x3) / 2
Gx = x1 + 2/3 × (Dx - x1) ← A 에서 D 쪽으로 2/3 이동
= 1/3 × x1 + 2/3 × Dx
= 1/3 × x1 + 2/3 × (x2 + x3) / 2
= (x1 + x2 + x3) / 3
세로 좌표도 똑같이 계산된다. 정리하면 삼각형의 무게중심은 다음과 같다.
G = ( (x1 + x2 + x3) ÷ 3, (y1 + y2 + y3) ÷ 3 )
세 꼭짓점 좌표를 각각 더해 3 으로 나눈 값, 즉 세 꼭짓점의 평균이다. 2 : 1 이라는 기하학적 성질이 '평균'이라는 단순한 계산으로 바뀌는 셈이다.
4단계: CSS 박스에 대입하기
이제 공식에 border 삼각형의 좌표를 넣어보자. 박스 왼쪽 위를 (0, 0), 가로를 W, 세로를 H 라고 하면
▲ 의 꼭짓점은 다음과 같다. CSS 와 마찬가지로 세로 좌표는 아래로 갈수록 커진다.
- A(위 꼭짓점):
(W/2, 0) - B(왼쪽 아래):
(0, H) - C(오른쪽 아래):
(W, H)
세 좌표를 각각 평균 내면 다음과 같다.
- 가로:
(W/2 + 0 + W) ÷ 3 = W/2→ 50% - 세로:
(0 + H + H) ÷ 3 = 2H/3→ 66.67%
세로 계산을 보면 세 꼭짓점 가운데 두 개가 밑변(H)에 있고, 위에 있는 건 하나(0)뿐이다.
세 점 중 두 점이 아래에 있으니 평균도 아래로 끌려 내려간다. 66.67% 는 이렇게 나온 값이다.
또 하나 눈여겨볼 점은 세로 계산에 가로 길이 W 가 등장하지 않는다는 것이다.
넓적하든 뾰족하든, 좌우가 대칭인 이등변삼각형이라면 무게중심은 항상 위에서 66.67% 지점에 있다.
아래에서 가로와 세로를 바꿔가며 직접 확인해 보자. 좌표는 크기에 따라 바뀌지만, 비율은 언제나 50%, 66.67% 다.
- A
- (W/2, 0) = (60, 0)
- B
- (0, H) = (0, 120)
- C
- (W, H) = (120, 120)
- G
- ((60 + 0 + 120) ÷ 3, (0 + 120 + 120) ÷ 3)
= (60, 80)
넓이를 반으로 나누는 지점은 아니다
여기서 흔히 하는 오해가 있다. 무게중심을 '위아래 넓이가 똑같아지는 높이'라고 생각하는 것이다.
하지만 66.67% 지점에서 가로로 자르면 위쪽 조각의 넓이는 전체의 (2/3)² = 4/9, 약 44%에 불과하다.
넓이를 정확히 반으로 나누는 높이는 위에서 약 70.7%(1/√2) 지점으로, 무게중심보다 조금 더 아래다.
무게중심은 넓이만이 아니라 그 넓이가 균형점에서 얼마나 멀리 떨어져 있는지까지 따진다. 시소를 떠올리면 쉽다. 아래쪽 넓은 조각은 균형점 가까이에 몰려 있고, 위쪽의 작은 조각은 뾰족한 꼭짓점까지 길게 뻗어 있어 지렛대 효과를 크게 받는다. 넓이는 위가 적지만 멀리 있는 만큼 무게를 더 받아, 66.67% 지점에서 양쪽이 균형을 이룬다.
해결: 회전 축을 무게중심으로
원인을 알았으니 해결은 간단하다. 회전 축을 박스 중심이 아니라 무게중심으로 옮기면 된다.
.arrow {
width: 0;
height: 0;
border-inline: 6px solid transparent;
border-bottom: 10px solid currentColor;
transform-origin: 50% 66.67%;
transition: transform 0.25s ease;
}
.toggle[aria-expanded="false"] .arrow {
transform: rotate(180deg);
}
transform-origin 의 퍼센트 값은 테두리까지 포함한 박스(border box)를 기준으로 계산된다.
그래서 width·height 가 0이어도 border 로 만든 12×10 크기를 기준으로 정확히 무게중심을 가리킨다.
앞서 확인했듯 가로 길이와 무관한 값이니, 화살표 크기를 바꿀 때도 그대로 두면 된다.
방향별 transform-origin
삼각형이 어느 방향을 가리키든 원리는 같다. 꼭짓점에서 밑변 쪽으로 2/3 지점이 무게중심이다.
| 그린 모양 | 색을 칠한 border | transform-origin |
|---|---|---|
| ▲ | border-bottom | 50% 66.67% |
| ▼ | border-top | 50% 33.33% |
| ▶ | border-left | 33.33% 50% |
| ◀ | border-right | 66.67% 50% |
기준이 되는 값은 회전하기 전, 처음 그린 모양의 무게중심이다.
▼ 를 그려두고 펼칠 때 ▲ 로 뒤집는 구조라면 50% 33.33% 를 쓴다.
박스는 대신 움직인다
회전 축을 옮기면 무게중심은 제자리에 머물지만, 그 대가로 박스가 움직인다. 위의 도해 ③ 처럼 뒤집힌 상태에서는 박스가 원래 자리보다 높이의 1/3 만큼 아래로 내려간다.
transform 은 레이아웃에 영향을 주지 않으므로 주변 요소가 밀려나지는 않는다.
다만 화살표를 감싼 요소에 overflow: hidden 이 걸려 있고 여유 공간이 빠듯하다면,
삐져나온 부분이 잘릴 수 있으니 확인이 필요하다.
한 걸음 더: 글자 중심선에 맞추기
flex 의 align-items: center 로 화살표를 글자 옆에 세로 가운데 정렬하면, 정렬 기준은 박스 중심이다.
그런데 무게중심(66.67%)은 박스 중심(50%)보다 16.67%, 즉 높이의 1/6 만큼 아래에 있다.
그래서 회전 축을 옮긴 화살표는 흔들리지는 않지만, 글자 중심선보다 살짝 아래에서 뒤집힌다.
흔히 이럴 때 '1px 올려보기'를 시도하지만, 무게중심을 알고 있으니 옮길 거리도 계산할 수 있다. 무게중심과 박스 중심의 차이, 즉 높이의 1/6 만큼 끌어올리면 된다.
.arrow {
/* ... */
transform-origin: 50% 66.67%;
translate: 0 -16.67%;
}
개별 변환 속성인 translate 는 transform 과 따로 지정하므로 rotate(180deg) 를 덮어쓰지 않는다.
둘이 합쳐질 때는 회전이 먼저 무게중심을 축으로 일어나고, 그 결과 전체가 1/6 만큼 위로 옮겨진다.
접혀 있든 펼쳐져 있든 무게중심은 늘 박스 중심, 곧 글자 중심선 위에 놓인다.
아래 데모에서 가이드 보기를 켜면 버튼의 세로 중앙, 즉 글자 중심선에 점선이 그어진다. 왼쪽은 무게중심 점이 선보다 아래에 머물고, 오른쪽은 선 위에 머문다. 차이가 1~2px 남짓이라 크게를 함께 켜면 확인하기 쉽다.
transform-origin: 50% 66.67%;transform-origin: 50% 66.67%;translate: 0 -16.67%;정리
- border 로 만든 삼각형은 박스 중심과 무게중심이 다르다. ▲ 의 무게중심은 위에서 66.67% 지점이다.
rotate(180deg)는 기본적으로 박스 중심을 축으로 돌기 때문에, 뒤집을 때마다 무게중심이 높이의 1/3 만큼 위아래로 오간다. 눈은 무게중심을 보므로 이것이 출렁임으로 느껴진다.transform-origin을 무게중심으로 옮기면 화살표가 제자리에서 뒤집힌다. 값은 삼각형의 폭과 상관없이 꼭짓점에서 밑변 쪽으로 2/3 지점이다.- 글자 중심선 같은 정렬 기준에 맞출 때도 눈대중 대신 무게중심과 기준선의 거리를 계산해 옮긴다.
시각 보정은 흔히 '감'의 영역으로 여겨진다. 하지만 눈이 무엇을 기준으로 위치를 판단하는지 알고 나면, 감으로 찾던 값은 계산으로 구할 수 있는 값이 된다. 무게중심을 이해하고 쓰면 크기가 바뀌어도 다시 맞출 필요가 없고, 왜 그 값인지 누구에게든 설명할 수 있다. 몇 픽셀을 이리저리 옮겨보는 대신, 기준을 먼저 세우자.
더 알아보기
- 무게 중심 (기하학) — 위키백과. 중선, 2 : 1 비율, 꼭짓점 좌표 평균 공식
- 중선 — 위키백과
- Centroid § Of a triangle — Wikipedia. 삼각형 무게중심을 구하는 여러 방법
- Centroid § Of a polygon — Wikipedia. 꺾쇠(⌄)처럼 삼각형이 아닌 아이콘의 무게중심을 구할 때
- transform-origin — MDN
- translate — MDN