한 줄 요약 —
transform은 요소를 레이아웃 자리에서 떼지 않은 채 보이는 모습만 옮기고·돌리고·키우는 CSS다. 벽에 박은 못(레이아웃 위치)은 그대로 두고 액자만 비스듬히 기울이는 셈이라,translate로 옮겨도 옆 요소가 밀리지 않는다.translate(이동)·rotate(회전)·scale(확대·축소)·skew(기울이기)를 한 줄에 이어 쓸 수 있고, 회전·확대의 기준점은transform-origin으로 바꾼다.
학습 목표#
transform이 레이아웃 자리를 그대로 두고 겉모습만 바꾼다는 것을 이해할 수 있다.translate로 옮기는 것과margin·top으로 옮기는 것의 차이를 설명할 수 있다.rotate·scale·skew로 돌리고 키우고 기울일 수 있다.transform-origin으로 회전·확대의 기준점을 바꿀 수 있다.- 카드에 호버하면 살짝 떠오르는 효과를
transform으로 만들 수 있다.
오늘의 비유 — 못은 그대로 두고 액자만 기울이는 일#
벽에 액자 하나가 걸려 있다고 하자. 액자를 건 못의 자리는 정해져 있고, 그 옆에는 다른 액자들이 간격을 맞춰 걸려 있다. 이제 이 액자를 살짝 비스듬히 기울이거나, 오른쪽으로 조금 밀거나, 더 크게 보이게 하고 싶다. 못을 다시 박지 않고도 액자 자체만 기울이고 옮기면 된다. 못은 제자리니까 옆 액자들은 꿈쩍도 하지 않는다.
transform이 바로 이 일이다. 요소가 레이아웃에서 차지한 자리(못)는 그대로 두고, 보이는 액자만 옮기고·돌리고·키운다. 그래서 transform으로 액자를 오른쪽으로 밀어도, 그 자리는 원래대로 비워져 있고 옆 요소는 밀리지 않는다. 이게 margin이나 top으로 옮길 때와 결정적으로 다른 점이다 — 그쪽은 못 자체를 옮기는 일이라 옆 액자들이 따라 움직인다.
핵심 개념#
transform — 자리는 그대로, 겉모습만 바꾼다#
transform은 한 속성 안에 여러 변형을 띄어쓰기로 이어 적는다. 가장 단순한 형태부터 보자.
.box {
transform: translate(20px, 0); /* 오른쪽으로 20px */
}
여기서 핵심은 .box가 레이아웃상으로는 안 움직였다는 점이다. 화면에서는 오른쪽으로 20px 가 있지만, 브라우저가 잡아 둔 원래 자리는 그대로다. 그래서 옆 요소가 밀리지 않고, 브라우저도 레이아웃을 다시 계산하지 않아 보통 더 가볍게 처리한다. 이 "자리는 유지, 겉모습만 변형"이 transform의 정체성이다.
translate — 옆을 안 밀고 미끄러뜨리기#
translate(x, y)는 액자를 가로·세로로 미끄러뜨린다. 가로만 옮기려면 translateX, 세로만이면 translateY를 쓴다. 양수 y는 아래로, 음수 y는 위로 간다.
.up { transform: translateY(-8px); } /* 위로 8px 떠오름 */
.right { transform: translateX(20px); } /* 오른쪽으로 20px */
margin-top: -8px로도 위로 올릴 수 있지만, 그건 못을 옮기는 일이라 아래 요소가 8px 따라 올라온다. translateY(-8px)는 액자만 올리므로 주변이 그대로다. 그래서 호버 효과처럼 잠깐 움직였다 돌아오는 연출에는 translate가 훨씬 잘 맞는다.
rotate와 scale — 돌리고 키우기#
rotate는 액자를 돌린다. 단위는 각도 deg이고, 양수는 시계 방향이다. scale은 액자를 키우거나 줄인다. 1이 원래 크기, 1.1은 10% 크게, 0.9는 10% 작게다.
.tilt { transform: rotate(8deg); } /* 시계 방향 8도 */
.big { transform: scale(1.1); } /* 1.1배 확대 */
scale도 레이아웃 자리는 원래 크기 그대로 둔 채 보이는 크기만 키운다. 그래서 1.1배로 키워도 주변이 밀리지 않고, 살짝 겹쳐 보일 수 있다. 둘을 한 번에 주려면 한 줄에 이어 쓴다.
.pop { transform: translateY(-8px) scale(1.05); }
이때 순서가 의미를 갖는다. 왼쪽부터 차례로 적용되므로, 먼저 옮기고 키우는 것과 먼저 키우고 옮기는 것은 결과가 다를 수 있다. 보통은 위 순서(이동 → 확대)면 무난하다. (skew는 액자를 평행사변형처럼 비스듬히 미는 변형인데, 실무에서는 드물게 쓰니 이런 게 있다는 정도만 기억해 두자.)
transform-origin — 어느 점을 기준으로 도는가#
회전·확대에는 기준점이 있다. 기본값은 액자의 한가운데(50% 50%)라, rotate를 주면 액자가 정중앙을 축으로 돈다. 못을 액자의 어느 구석에 박았는지 바꾸는 것처럼, 이 축을 옮기려면 transform-origin을 쓴다.
.corner {
transform-origin: top left; /* 왼쪽 위 모서리를 축으로 */
transform: rotate(8deg);
}
top left로 두면 왼쪽 위 모서리에 못을 박고 돌리는 셈이라, 같은 8도라도 액자가 도는 모양이 한가운데를 축으로 돌 때와 전혀 다르다. "회전이 이상하게 된다" 싶을 때 십중팔구 이 기준점 문제다.
함께 따라하기 — 호버하면 살짝 떠오르는 카드#
카드에 마우스를 올리면 액자가 벽에서 살짝 떠오르듯 위로 6px 올라오고, 아주 약간 커지는 효과를 만들어 본다.
<article class="card">
<h3>오늘의 메모</h3>
<p>마우스를 올려 보세요.</p>
</article>
.card {
padding: 20px;
border-radius: 12px;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.card:hover {
transform: translateY(-6px) scale(1.02);
}
저장하고 카드 위에 마우스를 올리면, 카드가 제자리에서 위로 6px 떠오르며 살짝 커진다. 이때 카드가 차지한 자리(못)는 그대로라 옆 카드들은 미동도 없다 — 떠오른 건 액자뿐이다. 마우스를 치우면 원래 자리로 내려온다. (다음 회차에서 배울 transition을 한 줄 더하면 이 움직임이 툭 끊기지 않고 부드럽게 이어진다.)
흔한 실수 3가지#
1. 옮기는 데 transform 대신 margin·top만 쓴다#
요소를 잠깐 움직이는 효과인데 margin이나 top으로 처리하는 경우다.
/* (옆 요소가 같이 밀리고, 매번 레이아웃을 다시 계산해 무겁습니다) */
.card:hover {
margin-top: -6px;
}
margin-top을 바꾸면 못 자체가 올라가는 셈이라, 아래에 있던 카드들이 6px씩 따라 올라와 화면이 들썩인다. 게다가 위치가 바뀔 때마다 브라우저가 레이아웃을 다시 계산한다. 단순히 보이는 자리만 옮기는 연출이라면 transform이 맞다.
.card:hover {
transform: translateY(-6px); /* 옆을 안 밀고 액자만 올린다 */
}
2. transform-origin을 안 바꿔 엉뚱한 축으로 돈다#
라벨을 카드 한쪽 모서리에 핀으로 꽂아 살짝 기울이고 싶은데, 기본 기준점을 모르고 돌리는 경우다.
/* (한가운데를 축으로 돌아, 모서리 회전을 기대하면 어긋납니다) */
.label {
transform: rotate(20deg);
}
모서리에 못을 박고 비스듬히 매단 라벨을 기대했는데, 기본 기준점이 정중앙이라 라벨이 한가운데를 축으로 빙 돈다. 원하는 모서리에 못을 박듯 transform-origin을 먼저 정해 준다.
.label {
transform-origin: top left;
transform: rotate(20deg);
}
3. translate(-50%, -50%) 가운데 정렬만 고집한다#
요소를 정중앙에 두는 고전 기법으로, position과 translate를 함께 쓴다.
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
부모 기준으로 top: 50%만큼 절반 내린 뒤, 자기 크기의 절반(-50%)만큼 되돌려 정확히 가운데를 맞추는 방법이다. 잘 동작하지만 줄이 길고 position까지 얽혀 손이 많이 간다. 요즘은 부모에 display: flex를 주고 justify-content·align-items로 가운데를 맞추는 쪽이 훨씬 간단하다. 이 translate 기법은 부모를 건드리기 어려운 특정 상황에서 꺼내 쓰는 카드로 남겨 두면 된다.
.parent {
display: flex;
justify-content: center;
align-items: center;
}
오늘 배운 것 체크리스트#
-
transform은 레이아웃 자리를 그대로 두고 보이는 모습만 바꾼다. -
translate로 옮기면 옆 요소가 밀리지 않는다 (margin·top과의 차이). -
rotate는 각도(deg),scale은 배율(1=원래 크기)로 돌리고 키운다. - 여러 변형은 한 줄에 이어 쓰며, 적용 순서가 결과에 영향을 준다.
- 회전·확대의 기준점은
transform-origin으로 바꾼다.
자주 묻는 질문#
Q. translate로 옮기는 것과 margin으로 옮기는 것은 뭐가 다른가요?
A. margin은 요소가 레이아웃에서 차지하는 자리(못) 자체를 옮겨, 옆이나 아래 요소가 따라 밀립니다. 반면 translate는 자리는 그대로 둔 채 보이는 모습만 옮기므로 주변이 미동도 없고, 브라우저가 레이아웃을 다시 계산하지 않아 보통 더 가볍습니다. 호버처럼 잠깐 움직였다 되돌아오는 연출에는 translate가 맞습니다.
Q. 회전 기준점(중심)을 모서리로 바꾸려면 어떻게 하나요?
A. transform-origin을 씁니다. 기본값은 요소 한가운데인 50% 50%라 rotate가 정중앙을 축으로 돕니다. transform-origin: top left처럼 키워드나 퍼센트로 축을 옮기면, 같은 각도라도 그 점을 중심으로 돌아 모양이 달라집니다. 회전이 의도와 다르게 보이면 가장 먼저 이 값을 확인하세요.
Q. 카드에 호버하면 살짝 떠오르거나 도는 효과는 어떻게 만드나요?
A. :hover에 transform을 주면 됩니다. 떠오르는 효과는 transform: translateY(-6px), 살짝 도는 효과는 transform: rotate(2deg)처럼 줍니다. 자리는 그대로라 옆 카드가 밀리지 않고, 다음 회차의 transition을 한 줄 더하면 움직임이 툭 끊기지 않고 부드럽게 이어집니다.
다음 시간 예고#
내일은 transition — 호버를 부드럽게, 그러나 너무 길지 않게를 다룬다. 오늘 만든 호버 효과가 툭 바뀌지 않고 자연스럽게 이어지도록, 변화에 걸리는 시간과 속도 곡선을 주는 법을 짚는다.