CSS 강좌 / transform과 transition으로 상태 변화 표현하기
transform은 요소의 보이는 위치·크기·각도를 바꾸고, transition은 상태가 바뀔 때 두 값 사이의 변화를 일정 시간에 걸쳐 보여 줍니다. 버튼의 호버처럼 시작과 끝이 정해진 변화에 두 기능을 함께 사용하면 됩니다. 변환은 주변 요소를 다시 배치하는 기능이 아니므로 화면의 움직임과 문서의 배치를 구분해야 합니다.
transform이 바꾸는 것과 바꾸지 않는 것
문서 흐름은 요소가 원래 배치된 자리와 주변 요소의 관계를 뜻합니다. 버튼을 translateY(-.5rem)으로 위로 옮겨도 아래 문단은 버튼이 이동한 공간을 따라 올라가지 않습니다. 변환된 요소가 주변 내용과 겹치거나 화면 밖으로 나갈 수 있으므로, 이동 거리를 크게 만드는 것보다 원래 배치에 충분한 여유를 두는 것이 먼저입니다.
| 함수 | 예 | 관찰할 결과 |
|---|---|---|
translate() |
translate(1rem, -.5rem) |
오른쪽으로 1rem, 위로 .5rem 이동 |
rotate() |
rotate(15deg) |
기준점을 중심으로 회전 |
scale() |
scale(1.05) |
원래 크기의 1.05배로 표시 |
크기가 커 보인다고 변환 전 박스가 차지하는 공간까지 커지는 것은 아닙니다. 카드의 실제 열 너비를 바꾸려면 Grid나 너비 속성을, 눌렀을 때 살짝 커지는 시각 효과를 만들려면 변환을 검토합니다. 일반적인 텍스트 span처럼 대체되지 않는 인라인 박스는 변환 대상이 아니므로 필요한 경우 display: inline-block을 지정합니다. 변환 대상과 좌표 처리의 정의는 MDN transform 문서에서 확인할 수 있습니다.
변환을 여러 개 쓰면 순서와 기준점이 중요합니다
변환 함수를 나열하면 각 함수가 다음 함수에서 사용할 좌표계에 영향을 줍니다. 따라서 이동·회전과 회전·이동은 대체로 같은 결과가 아닙니다. 아래 예제의 A는 오른쪽으로 이동한 자리에 회전한 박스가 보이고, B는 회전의 영향을 받은 방향으로 이동해 더 아래에 보입니다. 함수 이름만 보고 두 결과가 같다고 판단하지 말고 실제 위치를 비교하세요.
회전과 확대의 중심은 transform-origin으로 정합니다. 기본 중심 대신 left top을 사용하면 왼쪽 위 모서리를 기준으로 회전합니다. 메뉴가 열리는 느낌처럼 특정 지점을 고정하고 싶을 때 유용하지만, 기준점만 바꾸어도 주변 내용과의 겹침이 달라질 수 있습니다.
다음 코드를 UTF-8의 transform-compare.html로 저장하고 브라우저에서 여세요. 외부 파일이나 라이브러리는 필요하지 않습니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>변환 순서와 기준점</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
.comparisons { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); gap: 1rem; }
.slot { padding: 1rem; border: 1px solid #8a9ba7; }
.frame { height: 9rem; display: grid; place-items: center; background: #f0f2f4; }
.box { width: 4rem; height: 2rem; display: grid; place-items: center; background: #166b92; color: white; }
.first { transform: translateX(2rem) rotate(45deg); }
.second { transform: rotate(45deg) translateX(2rem); }
.center { transform: rotate(30deg); transform-origin: center; }
.corner { transform: rotate(30deg); transform-origin: left top; }
</style>
</head>
<body>
<main>
<h1>같은 값도 순서와 기준점에 따라 달라집니다</h1>
<div class="comparisons">
<section class="slot"><h2>이동·회전</h2><div class="frame"><div class="box first">A</div></div></section>
<section class="slot"><h2>회전·이동</h2><div class="frame"><div class="box second">B</div></div></section>
<section class="slot"><h2>중심 기준</h2><div class="frame"><div class="box center">C</div></div></section>
<section class="slot"><h2>왼쪽 위 기준</h2><div class="frame"><div class="box corner">D</div></div></section>
</div>
</main>
</body>
</html>
- A와 B의 위치를 비교합니다.
.second의 함수 순서만 A와 같게 바꾸면 두 박스의 상대적인 위치가 같아집니다. 확인 후 원래 순서로 되돌립니다. - C와 D는 회전각이 같습니다.
.corner의transform-origin만center로 바꾸어 기준점의 차이를 확인하고left top으로 복원합니다. - C의 회전만
scale(1.2)로 바꾸어 확대를 비교합니다. 박스 바깥 영역은 커지지만 주변 구역이 재배치되지는 않습니다. 확인 후rotate(30deg)로 복원합니다.
transition에서 속성·시간·곡선·지연 선택하기
transition은 그 자체로 상태를 바꾸지 않습니다. 호버, 초점 또는 클래스 변경으로 CSS 값이 달라져야 변화가 시작됩니다. 효과를 줄 속성 이름, 지속 시간, 진행 곡선, 시작 지연을 지정하며, 지연을 생략하면 기본값은 0초입니다.
transition: transform 180ms ease 0ms, background-color 180ms ease 0ms;
여기서 180ms는 0.18초이며 ease는 같은 속도로만 진행하지 않고 시작과 끝을 부드럽게 연결하는 곡선입니다. linear는 일정한 속도로 변합니다. 먼저 짧은 시간과 지연 없는 상태를 사용한 뒤 필요에 따라 조절하세요. 반응이 늦어 보이는 버튼에 긴 지연을 넣으면 조작의 결과를 알아차리기 어려워질 수 있습니다.
기본 선택자에 transition을 두면 호버로 들어갈 때와 빠져나올 때 모두 적용됩니다. 호버 선택자에만 transition을 두면 호버가 해제될 때 transition 설정도 사라져 예상과 다른 복귀가 나타날 수 있습니다. all 대신 실제로 바꿀 속성을 명시하면 나중에 추가한 크기·간격까지 의도치 않게 움직이는 일을 줄일 수 있습니다. 모든 속성이 같은 방식으로 보간되는 것은 아니며, 여기서는 연속적으로 비교하기 쉬운 변환과 배경색을 사용합니다. MDN transition 문서가 각 설정을 설명합니다.
호버와 키보드 초점을 함께 확인하는 전체 예제
아래 코드를 transition-state.html로 저장하세요. 버튼 위에 마우스를 올리거나 Tab 키로 이동하면 버튼이 위로 움직이고 배경색이 바뀝니다. 초점에는 진한 파란 윤곽선을 따로 표시합니다. 버튼은 상태 변화를 관찰하는 실습용이며 누른 뒤 별도의 작업은 수행하지 않습니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>변환과 상태 변화 실습</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
.stage { padding: 2rem 1rem; border: 2px dashed #8a9ba7; }
.action { padding: .75rem 1.25rem; border: 1px solid #166b92; border-radius: .4rem;
color: #183044; background: #eaf4fa; transform: translateY(0);
transition: transform 180ms ease, background-color 180ms ease; }
.action:hover, .action:focus-visible { transform: translateY(-.5rem); background: #cfe8f4; }
.neighbor { margin-block: 1rem 0; padding: .75rem; background: #f0f2f4; }
@media (prefers-reduced-motion: reduce) {
.action { transition: none; }
.action:hover, .action:focus-visible { transform: none; }
}
</style>
</head>
<body>
<main>
<h1>버튼이 움직여도 아래 문단은 그대로</h1>
<p>버튼에 마우스를 올리거나 Tab 키로 초점을 옮겨 보세요.</p>
<div class="stage">
<button class="action" type="button">상태 확인</button>
<p class="neighbor">버튼 아래의 문단입니다. 버튼의 변환으로 위치가 밀리지 않습니다.</p>
</div>
</main>
</body>
</html>
- 아래 문단의 위쪽 위치를 기억한 뒤 버튼에 호버합니다. 버튼만 움직이고 문단은 같은 자리를 유지해야 합니다.
- 지속 시간 두 곳을
180ms에서800ms로 바꾸어 진입과 복귀를 관찰한 뒤 원래 값으로 돌립니다. - 시간은 그대로 두고 진행 곡선만
linear로 바꾸어 비교합니다. 그다음ease로 복원합니다. - 첫 번째 transition의 끝에
400ms지연만 추가합니다. 배경색과 이동의 시작 시점이 달라집니다. 확인 후 지연을 제거합니다. - Tab 키로 버튼에 초점을 두고 Shift+Tab으로 빠져나옵니다. 초점 윤곽선과 배경색이 조작 상태를 설명하는지 확인합니다.
움직임 감소 설정과 효과의 한계
사용자가 움직임을 줄이도록 설정한 경우 prefers-reduced-motion: reduce 조건에서 이동을 없애고 배경색과 초점 표시를 남깁니다. transition만 없애면 변화가 즉시 일어날 뿐 이동한 최종 위치는 남습니다. 위 예제는 장식적인 이동 자체도 불필요하다고 판단해 호버·초점의 transform까지 none으로 바꾼 것입니다. MDN 움직임 감소 설정 안내를 참고하세요.
운영체제 설정을 바꾸거나 브라우저 개발자 도구의 Rendering 패널에서 해당 미디어 특성을 재현해 두 상태를 비교합니다. 감소 설정에서는 버튼이 이동하지 않고 색과 초점 표시만 바뀌어야 합니다. 실습을 마치면 에뮬레이션을 기본 상태로 복원하세요.
transform의 값이 none이 아니면 새로운 쌓임 맥락을 만들고 내부 absolute·fixed 요소의 기준에도 영향을 줄 수 있습니다. 팝업이나 고정 요소가 예상과 다르게 겹친다면 부모의 변환 여부도 살펴보세요. 애니메이션이 부드럽다고 배치와 접근성까지 올바르다는 뜻은 아니므로, 주변 겹침·키보드 초점·움직임 감소를 함께 점검해야 합니다.










