본문 바로가기
StyleSheet/CSS

[CSS] CSS 애니메이션 완벽 가이드 (Transform, Transition, Keyframes)

반응형

CSS Transition(트랜지션) - 뚝뚝 끊기는 화면에 '시간'의 부드러움을 불어넣는 마술

자바스크립트(JS)의 화려함 없이, 오직 CSS와 HTML만으로 짜인 웹사이트를 처음 배포했을 때의 기억을 떠올려 보십시오. 파란색 버튼 위에 마우스를 :hover 하는 순간 배경색이 빨간색으로 바뀌도록 코드를 짰을 것입니다. 코드는 완벽하게 동작하지만, 결과물은 어딘가 묘하게 촌스럽고 투박하게 느껴집니다. 왜 그럴까요? 마우스가 올라가는 0.001초의 찰나에 파란색이 빨간색으로 '팍!' 하고 점프하듯 변해버리기 때문입니다. 현실 세계에서 사물이 이동하거나 변할 때는 무조건 '시간(Time)'이 걸립니다.

빛조차도 지구에 도달하는 데 8분이 걸리는데, 브라우저 화면의 픽셀이 시간의 흐름 없이 순간 이동(Teleport)을 해버리니 인간의 뇌는 본능적으로 그 UI를 '기계적이고 뻣뻣하다'라고 인식하는 것입니다. 이 차갑고 뻣뻣한 DOM 트리 요소들의 상태 변화(State Change) 과정에 '초 단위의 시간''속도의 굴곡(가속도)'을 불어넣어 생명력을 부여하는 궁극의 치트키, 그것이 바로 CSS Transition (트랜지션)입니다. 트랜지션 속성 하나만 잘 바를 줄 알아도 웹사이트의 체감 퀄리티가 10배는 상승합니다.

이번 글에서는 트랜지션을 구성하는 4가지 톱니바퀴 속성과, 애니메이션의 영혼을 결정짓는 타이밍 함수(Easing Function), 그리고 실무에서 절대 transition: all을 쓰면 안 되는 이유까지 상세히 알아보겠습니다.

1. 변화를 지연시켜라: Transition의 4가지 핵심 부품

트랜지션은 상태 A(평소)에서 상태 B(Hover 등)로 넘어갈 때, 중간 과정의 프레임(Frame)들을 브라우저가 알아서 계산(Interpolation)하여 부드러운 애니메이션을 만들어주는 기능입니다. 이를 완벽히 제어하기 위해서는 4가지 속성 부품을 세팅해야 합니다.

  • transition-property: 어떤 속성에 마법을 걸지 지목합니다. background-color, width, opacity 등. (※ display: none 같은 이분법적인 속성은 중간 과정이 존재할 수 없으므로 트랜지션이 아예 먹히지 않는 치명적 단점이 있습니다!)
  • transition-duration: 변화가 완료될 때까지 걸리는 '총 시간'입니다. 0.3s(0.3초)나 300ms 단위로 표기합니다. 실무에서는 너무 느리면 답답하고 너무 빠르면 눈이 아프기 때문에 UI 마이크로 인터랙션의 황금 타이밍인 0.2s ~ 0.4s를 가장 많이 씁니다.
  • transition-timing-function: 속도의 완급조절(가속도 곡선)입니다. 아래 2번 항목에서 자세히 다룹니다.
  • transition-delay: 마우스를 올리고 나서 '몇 초 뒤에' 출발할지 '대기 시간'을 설정합니다.

💡 실무 압축 공식: Shorthand

고수들은 저 4가지를 따로 쓰지 않고 한 줄로 우아하게 묶어서 사용합니다.

.btn {
    background-color: blue;
    /* transition: [property] [duration] [timing-function] [delay]; */
    transition: background-color 0.3s ease-in-out 0.1s; 
}
.btn:hover { background-color: red; } /* 0.1초 뜸들이다가 0.3초 동안 스무스하게 빨간색으로! */

2. 애니메이션의 영혼(Soul): 타이밍 함수 (Easing Functions)

0.5초 동안 박스가 왼쪽에서 오른쪽으로 100px 이동한다고 가정합시다. 처음부터 끝까지 똑같은 속도로 움직이면(linear), 로봇트나 PPT 애니메이션처럼 굉장히 딱딱하고 어색해 보입니다. 현실의 물리 법칙은 출발할 때는 엑셀을 밟아 서서히 빨라지고(가속), 도착할 땐 브레이크를 밟아 서서히 느려지기(감속) 때문입니다. 이를 CSS로 모방하는 것이 timing-function입니다.

  • linear: 가속도 0. 기계적으로 일정하게 움직입니다. 뱅글뱅글 영원히 돌아가는 로딩 스피너(Loading Spinner)에만 씁니다.
  • ease-in: 처음엔 느리다가 뒤로 갈수록 미친 듯이 빨라집니다. 화면에서 요소를 바깥으로 확 쫓아내 버릴 때(사라질 때) 씁니다.
  • ease-out: 처음엔 확 치고 나가다가 도착할 때쯤 스무스하게 멈춥니다. 화면 바깥에서 요소가 우리 눈앞으로 안착할 때(나타날 때) 쓰는 가장 안정적이고 대중적인 옵션입니다.
  • ease-in-out: 출발도 부드럽게, 도착도 부드럽게 합니다.

궁극의 커스텀: cubic-bezier()

기본 제공 단어들로 만족할 수 없다면, 베지어 곡선(Bezier Curve) 수학 공식을 이용해 가속도를 픽셀 단위로 커스텀할 수 있습니다. cubic-bezier(0.175, 0.885, 0.32, 1.275) 처럼 마지막 Y좌표를 1을 초과하는 값으로 줘버리면, 목적지에 도착했다가 브레이크가 밀려 띠용! 하고 스프링처럼 튀어나갔다 되돌아오는 환상적인 바운스(Bounce) 젤리 효과를 자바스크립트 물리 엔진 없이도 완벽하게 구현할 수 있습니다.

3. 초보들의 무덤: transition: all을 절대 쓰면 안 되는 이유

CSS 트랜지션을 처음 배운 주니어들은 너무 신난 나머지 모든 코드에 transition: all 0.3s;이라는 무적의 주문을 걸고 다닙니다. "이렇게 해두면 버튼 색깔 바뀌든 테두리가 굵어지든 알아서 다 부드럽게 변하겠지? 개꿀!"

이것은 브라우저 렌더링 성능(GPU)을 박살 내는 최악의 프론트엔드 안티 패턴(Anti-pattern)입니다. all을 걸어두면 브라우저는 그 요소의 수십 가지 CSS 속성 하나하나에 전부 애니메이션 대기 상태 메모리를 할당하고 감시해야 합니다. 게다가 마우스 호버 시 실수로 레이아웃에 관여하는 margin이나 width 속성이라도 변하게 된다면, 브라우저는 0.3초 동안 매 프레임마다 화면 전체 박스들의 크기와 위치를 다시 계산하는 '레이아웃 리플로우(Layout Reflow)' 폭탄을 맞고 프레임 드랍(버벅거림)이 발생하여 사이트가 완전히 멈춰버릴 수도 있습니다.

따라서 실무에서는 transition: background-color 0.3s, color 0.3s; 처럼 반드시 변화를 주어야 할 속성 이름만 정확하게 핀셋으로 집어내어(Targeting) 쉼표로 연결해야만 압도적으로 부드러운 60fps 애니메이션 퍼포먼스를 유지할 수 있습니다.

정리하며

이번 글에서는 딱딱한 웹사이트에 생명력을 불어넣는 시간의 마술, CSS Transition의 원리와 타이밍 곡선, 그리고 all 남용의 참사에 대해 상세히 살펴보았습니다. 마우스 호버(Hover) 시의 뻣뻣함은 모두 해결되었습니다. 그런데, 트랜지션은 치명적인 단점이 하나 있습니다. 무조건 사용자가 '마우스를 올리거나(Hover) 클릭(Active)을 해야만' 즉 트리거(Trigger)가 있어야만 발동하며, 그것도 상태 A에서 B로 딱 한 번만 변하고 멈춰버린다는 것입니다. 사용자가 아무 짓도 안 했는데 페이지에 접속하자마자 혼자서 배경이 빙글빙글 돌고 크기가 커졌다 작아졌다 춤을 추게 만들려면 어떻게 해야 할까요? 감독이 작성한 시나리오대로 배우(태그)가 무한히 연기하게 만드는 할리우드 영화 기법! 다음


CSS Animation & Keyframes - 할리우드 영화 기법으로 무한 루프 애니메이션을 연출하다

Concept Illustration

직전이번 포스팅에 서 다룬 트랜지션(Transition)은 정말 훌륭하고 부드럽지만, 결정적인 한계가 존재합니다. 트랜지션은 오직 '사용자의 마우스 클릭이나 호버 같은 조건(Trigger)'이 성립될 때만 발동하며, 평범한 상태 A에서 다른 상태 B로 딱 한 번 변하고 나면 그대로 멈춰버립니다. 만약 사용자가 웹사이트에 아무 짓도 안 하고 멍하니 쳐다만 보고 있는데도 메인 배너의 우주선이 화면 좌측에서 우측으로 지그재그 비행을 하거나, 로딩 아이콘이 360도로 영원히 빙글빙글 돌게 하려면 어떻게 해야 할까요? 상태 A에서 B로 끝나는 것이 아니라, A → B → C → D로 이어지는 복잡한 시나리오를 짜려면? 과거에는 자바스크립트의 setInterval 타이머 함수를 써서 1프레임마다 픽셀 위치를 강제로 갱신하는 렌더링 파괴 행위를 저질러야 했습니다. 하지만 이제는 아닙니다. W3C는 영화나 애니메이션 제작 소프트웨어(애프터 이펙트, 플래시 등)에서 사용하는 타임라인(Timeline) 방식을 CSS에 그대로 이식해 버렸습니다. 그것이 바로 'CSS Animation'과 @keyframes (키프레임)입니다. 자바스크립트 없이 그래픽 카드(GPU)의 힘만으로 화면에 블록버스터급 생명력을 불어넣는 궁극의 연출 기법! 이번 글에서는 @keyframes 대본을 작성하는 방법부터, 애니메이션의 반복, 지연, 방향 역재생(Alternate)을 통제하는 8가지 핵심 속성까지 다뤄보겠습니다.

1. 대본(Scenario)을 작성하라: @keyframes

CSS 애니메이션을 작동시키려면 먼저 영화 대본(시나리오)을 짜야 합니다. 애니메이션이 시작될 때부터 끝날 때까지 어떤 타이밍에 어떻게 변할지를 @keyframes라는 특수 문법 안에 퍼센트(%) 비율로 적어줍니다.

/* 'bounce-dance'라는 이름의 대본(키프레임)을 작성합니다 */
@keyframes bounce-dance {
    0% { /* 0% (시작점) */
        transform: translateY(0); 
        background-color: blue; 
    }
    50% { /* 50% (상영 시간의 절반 지점) */
        transform: translateY(-50px); /* 위로 50px 점프! */
        background-color: purple; /* 중간에 보라색으로 변신! */
    }
    100% { /* 100% (끝점) */
        transform: translateY(0); /* 다시 원래 위치로 바닥 착지! */
        background-color: red; /* 최종적으로 빨간색으로 마무리! */
    }
}

이 대본은 단독으로는 아무 일도 하지 못합니다. 이 대본을 연기할 '배우(HTML 태그)'를 캐스팅해서 대본을 쥐여주어야(연결해야) 비로소 애니메이션이 재생됩니다.

2. 배우를 캐스팅하고 큐사인(Cue)을 주다: animation 속성

만들어둔 대본 bounce-dance를 특정 <div class="box"> 요소에 연결하여 영화를 상영시켜 보겠습니다.

  • animation-name: 재생할 대본(키프레임)의 이름을 적습니다. animation-name: bounce-dance;
  • animation-duration: 영화의 상영 시간(총 재생 시간)입니다. 2s(2초)로 설정하면 0%부터 100%까지의 대본을 2초 만에 다 해치웁니다.
  • animation-timing-function: 트랜지션에서 배웠던 가속도 곡선(ease-in-out, linear 등)과 똑같습니다.

무한 루프와 역재생 통제

트랜지션에는 없는 애니메이션만의 독보적이고 강력한 제어 속성들입니다.

  • animation-iteration-count: 영화를 몇 번 반복 상영할지 결정합니다. 숫자(예: 3)를 적을 수도 있지만, 로딩 스피너처럼 영원히 돌게 하려면 infinite (무한대) 키워드를 적어줍니다. 자바스크립트 setInterval이 프론트엔드에서 멸종하게 된 1등 공신입니다.
  • animation-direction: 0%에서 100%로 재생이 끝난 뒤, 다음 반복 때 어떻게 재생할지 결정합니다. alternate를 주면 100%에 도달한 뒤 테이프를 감듯이 100%에서 0%로 역재생(거꾸로)하며 돌아옵니다. 점프(Bounce) 효과를 아주 자연스럽게 만들 때 필수입니다.
  • animation-fill-mode: 애니메이션이 끝나고 난 뒤 요소의 상태를 어떻게 처리할지 결정합니다. 기본적으로 애니메이션이 100% 끝나면 요소는 '애니메이션 재생 전의 원래 상태(쌩얼)'로 휙! 하고 돌아가 버립니다. 이것을 막고 100% 상태 그대로 영원히 정지(유지)하게 하려면 forwards 속성을 반드시 줘야 합니다. 반대로 0% 상태의 스타일을 애니메이션 시작(대기) 전부터 미리 적용시켜 놓으려면 backwards를 줍니다. (두 개 합쳐서 both도 가능)

💡 실무 압축 공식: Shorthand

.box {
    /* animation: [name] [duration] [timing-function] [delay] [iteration-count] [direction] [fill-mode]; */
    animation: bounce-dance 2s ease-in-out 0.5s infinite alternate forwards; 
}

3. 실전 해킹: pause(일시정지) 꼼수

영원히 빙글빙글 도는 톱니바퀴 애니메이션이 있다고 가정해 봅시다. 그런데 사용자가 마우스를 올리면(Hover) 톱니바퀴가 딱 그 각도에서 일시정지하게 만들고 싶습니다. 자바스크립트를 써야 할까요? 아닙니다! animation-play-state: paused; 속성을 호버 시에 주면 애니메이션을 영화 일시정지 버튼 누르듯 멈출 수 있고, 마우스를 떼면(기본값 running) 멈췄던 지점에서 다시 부드럽게 이어서 재생됩니다.

정리하며

이번 글에서는 트랜지션의 한계를 부수고 완벽한 0% ~ 100% 타임라인 시나리오를 연출하는 @keyframes 애니메이션의 놀라운 통제력을 확인했습니다. infinite 무한 루프와 alternate 역재생, 그리고 잔상을 유지하는 forwards의 위력을 조합하면 구현 못 할 인터랙션이 없습니다. 그런데 눈치채셨나요? 제가 애니메이션 대본(키프레임) 예시 코드 안에서 위치를 이동시키기 위해 margin-top이나 top/left를 쓰지 않고 이상한 transform: translateY()라는 속성을 사용했다는 것을요. 프론트엔드 애니메이션에서 margin이나 left를 조작하여 요소를 이동시키는 것은 '브라우저 GPU를 박살 내는 중범죄' 취급을 받습니다. 오직 60fps의 부드러움을 보장하는 마법의 속성이 필요합니다. 다음이번 포스팅에서는 "CSS Transform: 레이아웃 엔진을 피해서 2D와 3D 공간을 비틀고 회전시키는 GPU 가속의 비밀"에 대해 상세히 파헤쳐 보겠습니다!


CSS Transform - 레이아웃 렌더링 엔진을 속여 60fps 애니메이션 최적화를 달성하다

이전 포스팅들을 통해 우리는 상태를 부드럽게 변화시키는 Transition과, 무한 루프 시나리오를 연출하는 Animation 기법을 완벽히 마스터했습니다. 자, 이제 배운 것을 써먹을 차례입니다. 버튼에 마우스를 올렸을 때 버튼이 오른쪽으로 부드럽게 50px 이동(슬라이드)하는 효과를 내보겠습니다. 당신은 자랑스럽게 transition: margin-left 0.3s;를 주고 호버 시 margin-left: 50px;을 입력합니다. 버튼은 오른쪽으로 훌륭하게 이동합니다. 하지만 모바일에서 접속하는 순간 끔찍한 현상을 마주하게 됩니다. 버튼이 미끄러지듯 이동하는 게 아니라, 오래된 로봇처럼 뚝.. 뚝.. 끊기며 버벅거리기(Frame Drop) 시작합니다. 심지어 버튼이 이동할 때마다 그 옆에 있던 텍스트와 이미지들이 밀려나면서 화면 전체가 요동칩니다. 왜 이런 비극이 일어날까요? 브라우저의 눈에는 margin이나 width, left/top 값이 1px이라도 변하는 순간, "앗! 요소의 부피(레이아웃)가 변했다! 화면에 있는 1,000개의 박스 위치를 처음부터 다 다시 계산해야 해!"라며 미친 듯이 수학 연산(Layout Reflow)을 다시 돌려버리기 때문입니다. 0.3초라는 짧은 시간 동안 브라우저는 이 무거운 레이아웃 계산을 60번이나 억지로 수행하다가 결국 그래픽 코어(GPU)가 뻗어버리고 버벅거리는 것입니다. 이 참사를 막기 위해 W3C가 내려준 프론트엔드 애니메이션의 궁극적인 렌더링 면죄부, 그것이 바로 'CSS Transform' 속성입니다. 이번 글에서는 브라우저 렌더링 트리를 완벽히 속이고 그래픽 카드(GPU) 가속을 100% 빨아먹는 Transform의 이동, 확대, 회전 마법과 하드웨어 가속 최적화 원리에 대해 상세히 알아보겠습니다.

1. 레이아웃 파괴 없이 자유자재로 조작하라: Transform의 기본 함수들

transform 속성은 요소를 이동시키거나, 크기를 키우거나, 돌려버리는 기능(함수)들을 제공합니다. Transform이 위대한 이유는 "자신이 조작되어 덩치가 2배로 커지거나 우측으로 500px을 날아가더라도, 브라우저가 인식하는 요소의 물리적인 '원본 위치와 크기(레이아웃)'는 처음 그 자리에 고스란히 남아있다고 착각하게 만든다"는 점입니다. 즉, 나 혼자 커지고 날아다녀도 옆에 있는 다른 박스들을 절대로 툭 쳐서 밀어내지(Reflow) 않는 완벽한 독립 레이어(Layer)가 됩니다.

① 이동: translate(X, Y)

요소를 X축(가로)과 Y축(세로)으로 이동시킵니다. translate(50px, 100px)를 주면 우측으로 50px, 아래로 100px 이동합니다. [🚨 실무 극강 꿀팁] 여기서 픽셀이 아니라 퍼센트 translate(50%, 0)를 주면 어떻게 될까요? 부모의 너비가 아니라 '자신(태그)의 실제 가로 너비'의 50%만큼을 계산하여 이동합니다! 앞서 포지션(Position) 편에서 배웠던 '공중에 뜬 박스 화면 정중앙에 완벽히 꽂아 넣기' 공식을 기억하시나요?

.modal {
    position: absolute;
    top: 50%; left: 50%; /* 부모(화면) 기준 50% 지점(정중앙)에 꼭짓점을 박음 */
    /* 하지만 모달창 자신의 크기 때문에 살짝 우측 하단으로 치우친 상태! */
    transform: translate(-50%, -50%); /* 내 몸집의 딱 절반(-50%)만큼 위, 왼쪽으로 빽(Back)해서 완벽한 정중앙을 맞춘다! */
}

translate(-50%, -50%) 콤보는 CSS가 멸망하는 그날까지 쓰일 프론트엔드 센터링(Centering)의 불패 공식입니다.

② 확대/축소: scale(배율)

widthheight 값을 건드리지 않고도 요소의 크기를 뻥튀기합니다. scale(1)이 원본 크기이고, scale(1.2)를 주면 1.2배 커집니다. 호버 시 버튼이나 이미지 카드가 살짝 다가오는(커지는) 마이크로 인터랙션을 구현할 때 폭발적으로 사용됩니다. (이때 이미지가 픽셀이 깨져 보이지 않도록 부모에 overflow: hidden을 걸어두는 센스가 필요합니다.)

③ 회전: rotate(각도)

요소를 회전시킵니다. deg(디그리, 각도) 단위를 사용합니다. rotate(45deg)를 주면 시계방향으로 45도 꺾이고, -45deg를 주면 반시계 방향으로 꺾입니다. 로딩 스피너를 무한 회전시킬 때 @keyframes 안에서 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); }의 형태로 밥 먹듯이 쓰입니다.

④ 비틀기: skewX, skewY

요소를 평행사변형처럼 찌그러뜨립니다. 디자인 요소가 아주 강렬한 스포티(Sporty)한 사이트의 사선 레이아웃이나 속도감을 표현하는 텍스트 이펙트 등에 가끔 쓰이는 조미료 같은 함수입니다.

2. 여러 개의 마법을 콤보로 엮기 (체이닝)

"우측으로 이동하면서 동시에 두 배로 커지고 빙글 돌아라!"라는 명령을 내리고 싶다면 어떻게 할까요? 절대 transform을 줄 바꿈 해서 여러 번 쓰면 안 됩니다. 맨 마지막에 쓴 한 줄만 적용(덮어쓰기)되어 버립니다. 반드시 한 줄에 스페이스바(공백)로 띄어쓰기하여 함수들을 체이닝(Chaining)해야 합니다.

.box:hover {
    /* 띄어쓰기로 여러 함수를 조합합니다. (콤마 쓰면 에러 남!) */
    transform: translateX(100px) scale(2) rotate(180deg);
}

3. 기준점의 지배자: transform-origin

우리가 rotate(45deg)로 박스를 돌릴 때, 기본적으로 그 박스의 정중앙(가운데 배꼽, 50% 50%)에 압정을 꽂아두고 돌리게 됩니다. 하지만 우리가 시계바늘이나 자동차 와이퍼 애니메이션을 만든다면 어떻게 될까요? 가운데를 기점으로 돌면 양 끝이 다 움직여버립니다. 와이퍼는 '맨 밑동(Bottom)'에 압정이 박힌 채로 움직여야 하죠. 이때 압정의 위치를 변경하는 속성이 바로 transform-origin입니다.

transform-origin: bottom center; (또는 50% 100%)를 주면, 압정이 박스의 맨 밑바닥 중앙에 꽂힙니다. 그 상태에서 rotate를 주면 와이퍼처럼 흔들거리는 디테일한 애니메이션 기준축이 완성됩니다.

정리하며

이번 글에서는 브라우저의 렌더링 지옥(Reflow)을 피하고 컴퓨터 그래픽 카드(GPU)의 축복을 받아 극강의 60fps 애니메이션 퍼포먼스를 뽑아내는 궁극의 렌더링 최적화 기술, transform의 3대장(translate, scale, rotate)을 완벽하게 파헤쳐 보았습니다. 이제 우리는 트랜지션, 애니메이션, 트랜스폼을 조합하여 지구상의 그 어떤 화려한 UI 인터랙션도 퍼포먼스 저하 없이 찍어낼 수 있는 장인이 되었습니다. 그런데, 박스가 화려하게 움직이다가 결국 다른 박스 위에 겹쳐버렸습니다! 누가 위로 올라와야 하고 누가 밑으로 깔려야 할까요? Z축(깊이) 공간을 통제하여 요소들의 쌓임 순서(레이어)를 조작하는 흑마법! 다음 실무 트러블슈팅 포스팅에서는 주니어 개발자들을 미치게 만드는 "CSS z-index 완벽 가이드: 9999를 남발해도 내 팝업이 위로 안 올라오는 Stacking Context의 저주"에 대해 상세히 자세히 알아보겠습니다.


CSS Transition - 부드러운 상태 변화로 UX를 완성하다

지금까지 우리가 작성한 CSS는 철저하게 '정적(Static)'이었습니다. 버튼에 :hover를 걸어 배경색을 파란색에서 빨간색으로 바꾸면, 사용자가 마우스를 올리는 바로 그 0.001초의 찰나에 색상이 번쩍! 하고 바뀌어 버렸죠. 이것은 기계적이고 차가운 느낌을 주며 시각적으로도 사용자에게 약간의 피로감을 유발합니다. 인간의 뇌는 물리 법칙이 적용되는 자연스러운 '변화의 과정'에 편안함을 느낍니다. 파란색이 서서히 보라색을 거쳐 0.3초 만에 빨간색으로 스르륵 물드는 부드러움, 버튼이 클릭될 때 0.1초 동안 살짝 찌그러졌다가 튀어 오르는 탄성! 이 모든 버터 같은 부드러움을 단 한 줄의 CSS 코드로 가능하게 해주는 기적의 속성이 바로 CSS Transition(트랜지션)입니다. 자바스크립트 타이머(setInterval)로 픽셀을 쪼개가며 애니메이션을 만들던 고대 개발자들을 구원한 Transition의 핵심 원리와 4가지 세부 파라미터를 상세히 알아보겠습니다.

1. 0초의 번쩍임을 0.3초의 예술로: transition 필수 속성

Transition은 요소의 CSS 속성 값이 변할 때, 즉각적으로 변하지 않고 **지정된 시간(Duration) 동안 부드럽게 '보간(Interpolation)'**되도록 브라우저 렌더링 엔진에 지시를 내립니다.

① transition-property (누구를 변화시킬 것인가?)

마우스 호버 시 배경색(background-color)과 글자색(color)이 동시에 변한다고 가정합시다. 이때 background-color만 부드럽게 변하고 글자색은 번쩍 바뀌게 콕 집어서 타겟을 지정할 수 있습니다. 하지만 실무에서는 귀찮기 때문에 보통 all (변화하는 모든 속성)을 선언해 버립니다.

② transition-duration (얼마나 걸릴 것인가?)

애니메이션이 시작해서 끝날 때까지의 총 재생 시간입니다. 초(s) 단위나 밀리초(ms) 단위를 씁니다. 인간의 눈에 가장 자연스럽고 쾌적한 마우스 호버 효과 시간은 0.2s ~ 0.3s로 실무에서 거의 국룰처럼 고정되어 쓰입니다. 너무 길어지면(예: 1s) 웹사이트가 굼뜨고 무겁게 느껴집니다.

2. 타이밍과 속도감을 지배하라: transition-timing-function

같은 0.3초라도 일정한 속도로 기계처럼 변할 것인가, 아니면 자동차가 출발하듯 처음엔 느리다가 중간에 훅 빨라질 것인가? 애니메이션의 '감칠맛(Easing)'을 결정하는 가장 중요한 속성입니다.

  • linear: 기계처럼 처음부터 끝까지 속도가 완전히 똑같습니다. 로딩 스피너 빙글빙글 돌릴 때 빼고는 잘 안 씁니다.
  • ease (기본값): 시작할 땐 살짝 부드럽고, 중간에 훅 빨라졌다가, 끝날 때 아주 천천히 스무스하게 멈춥니다. 가장 자연스럽습니다.
  • ease-in-out: 처음과 끝이 대칭으로 똑같이 부드러운 형태입니다.
  • cubic-bezier(n,n,n,n): 끝판왕! 곡선을 내 마음대로 조작합니다. 심지어 값을 1 이상으로 줘서 목표점보다 더 튀어나갔다가 다시 돌아오는 '통통 튀는 바운스(Bounce) 효과'도 이 베지어 곡선으로 만들어냅니다!

3. 실무형 축약(Shorthand) 코드와 적용 위치 주의점

이 4가지 속성을 매번 다 적으면 코드가 너무 길어집니다. transition 하나로 압축합니다.

.btn {
    background-color: blue;
    /* 💡 실무 정석: 대상(all), 시간(0.3s), 곡선(ease) 순으로 축약 */
    /* 주의!! hover 쪽이 아니라 '기본 상태(.btn)'에 부여해야 마우스를 뗄 때도 스르륵 돌아옵니다! */
    transition: all 0.3s ease; 
}
.btn:hover {
    background-color: red; /* 상태 B (타겟) */
}

초보자들이 가장 많이 하는 실수가 transition 속성을 .btn:hover { } 괄호 안에 넣는 것입니다. 그렇게 하면 마우스를 올릴 땐 부드럽지만, 마우스를 떼는 순간 0초 만에 원래 상태로 번쩍하고 돌아와 버리는 반쪽짜리 효과가 됩니다. 트랜지션은 반드시 변화의 주체인 '기본(원형) 클래스'에 달아주어야 왕복이 모두 부드러워집니다.

정리하며

transition: 0.3s; 단 한 줄의 코드는 딱딱한 기계 덩어리 웹페이지에 생명체 같은 부드러움을 불어넣는 궁극의 마취제입니다. 색상을 서서히 바꾸고, 그림자를 부드럽게 퍼뜨리는 법을 알게 되었습니다. 그런데 잠깐, "버튼이 커진다거나, 빙글빙글 회전한다거나, 화면 밖에서 휙 날아오는" 움직임은 어떻게 구현할까요? widthmargin을 트랜지션 시키면 브라우저 렌더링 성능이 바닥을 치게 됩니다. 다음이번 포스팅에서는 브라우저의 GPU(그래픽 카드)를 강제로 빌려와서 60 프레임의 부드러움으로 요소를 3차원으로 깎고 비트는 괴물 같은 기술, "Transform: 2D/3D 공간 변형 (translate, scale, rotate)"에 대해 상세히 파헤쳐 보겠습니다!


CSS Transform - 브라우저 GPU를 태우는 2D/3D 시공간 변형술

버튼에 마우스를 올렸을 때 버튼이 커지는(Zoom-in) 효과를 만들고 싶다고 가정해 봅시다. 가장 먼저 떠오르는 방법은 호버(Hover) 시 widthheight의 px 수치를 늘리는 것입니다. 하지만 이 방식은 끔찍한 결과를 낳습니다. width가 커지는 순간 옆에 있던 다른 요소들이 죄다 밀려나면서 화면 전체의 레이아웃이 덜컹거리는 '리플로우(Reflow)' 연산 폭탄이 발생하기 때문입니다. 모바일에서는 화면이 버벅(Jank)거리다 못해 프리징이 걸리기도 합니다. "주변 요소들은 아무도 건드리지 않고, 내 몸만 공중으로 띄워서 확대하고, 회전시키고, 비틀어버릴 수는 없을까?" 이 욕망을 실현하기 위해 탄생한 궁극의 렌더링 최적화 기술이 바로 CSS Transform입니다. Transform은 브라우저의 CPU 대신 **그래픽 카드(GPU)**를 직접 갈구어 연산하므로 60fps의 미끄러지는 듯한 극강의 애니메이션 프레임을 보장합니다. 이번 글에서는 요소를 요리조리 주무르는 translate, scale, rotate 삼총사의 마법을 상세히 알아보겠습니다.

1. 공간 이동의 지배자: translate()

요소를 현재 자기 위치를 기준으로 X축과 Y축으로 이동시킵니다. position: relativetop, left를 써서 이동시키는 것과 결과는 같아 보이지만 렌더링 성능 차이는 하늘과 땅 차이입니다. 애니메이션으로 위치를 옮길 때는 무조건 translate를 써야 합니다.

.box:hover {
    /* 마우스 호버 시 위로(Y축 음수) 10px 붕 띄워라! */
    /* 주변 요소들은 박스가 위로 가든 말든 전혀 눈치채지 못하고 제자리에 있음! */
    transform: translateY(-10px); 
}

💡 실무 1순위 치트키: 퍼센트(%)의 마법과 정중앙 정렬

translate 괄호 안의 퍼센트(%)는 부모 크기가 아니라 '자신의 너비와 높이'를 기준으로 합니다. 이를 이용해 position: absolute; top: 50%; left: 50%;로 대충 중앙에 던져놓은 모달 창을 transform: translate(-50%, -50%);로 땡겨오면 요소의 크기를 몰라도 완벽한 정중앙 배치가 픽셀 단위로 완성됩니다!

2. 크기 조작과 회전의 예술: scale() & rotate()

스케일(scale): 너비가 아니라 배율로 조작하라

scale(1)이 원본 100% 크기입니다. 1.5는 1.5배 확대, 0.5는 반토막 축소입니다. 쇼핑몰에서 상품 썸네일(사진)에 마우스를 올리면 사진이 확대되는(Zoom-in) 효과는 전부 이 코드로 짭니다.

.thumb-img {
    transition: transform 0.3s;
}
.thumb-img:hover {
    /* 💡 width를 늘리지 않고 비율(Scale)로만 1.1배 확대 (레이아웃 붕괴 없음) */
    transform: scale(1.1); 
}

회전(rotate): 각도(deg)의 미학

요소를 시계 방향(양수) 또는 반시계 방향(음수)으로 빙글 회전시킵니다. deg(Degree, 각도) 단위를 사용합니다. 로딩 중인 톱니바퀴 아이콘을 돌리거나, 아코디언 메뉴의 꺾쇠(▼) 아이콘을 180도 뒤집어서 위 화살표(▲)로 만들 때 핵심적으로 씁니다.

3. 전문가의 관문: 다중 변형과 순서의 비밀 (Chaining)

이동도 하고, 확대도 하고, 회전도 동시에 하고 싶다면? 쉼표 없이 띄어쓰기로 속성들을 이어 붙이면(Chaining) 됩니다.

.magic-box {
    /* 100px 우측으로 이동한 다음 -> 1.5배 키우고 -> 45도 비틀어라! */
    transform: translateX(100px) scale(1.5) rotate(45deg);
}

[🚨 치명적 주의점] 브라우저는 이 명령어들을 **'오른쪽에서 왼쪽으로' (역순으로)** 읽으며 계산하거나, 혹은 축(Axis) 자체가 변형된다고 생각해야 합니다. rotate를 먼저 적어버리면 요소의 X축, Y축 자체가 대각선으로 틀어져버려서 그다음에 translateX(100px)를 주면 직진이 아니라 대각선으로 날아가 버리는 기괴한 버그를 마주하게 됩니다. 순서가 렌더링 결과를 완전히 바꿉니다!

4. 덤: 3D의 세계로 (perspective)

rotateX(), rotateY()를 사용해 카드가 앞뒤로 휙휙 뒤집히는 3D 플립(Flip) 애니메이션도 만들 수 있습니다. 단, 부모 요소에 반드시 perspective: 1000px; 같은 '원근감(카메라 거리)' 수치를 주어야 평면 모니터에서 실제 3D 입체감이 느껴지며 렌더링 됩니다.

정리하며

CSS Transform은 화면의 레이아웃(Reflow) 비용을 0으로 억제하면서, 오직 GPU의 합성(Composite) 기능만으로 요소를 가장 빠르고 화려하게 주무르는 프론트엔드 애니메이션의 궁극적 해답입니다. 하지만 transitiontransform의 조합은 오직 '상태 A에서 상태 B로' 한 번만 변하고 끝난다는 단점이 있습니다. "처음에 커졌다가, 중간에 파란색이 되고, 마지막에 3바퀴를 돌면서 영원히 무한 반복해 줘!" 같은 복잡한 시나리오는 불가능하죠. 이를 해결하기 위해 영화 필름을 돌리듯 프레임을 조각조각 깎아내는 기술이 필요합니다. 다음이번 포스팅에서는 CSS 애니메이션의 감독(Director)이 되어 타임라인을 통제하는 기적의 문법, "Keyframes: 복잡한 시퀀스 애니메이션 제작"에 대해 뼈 때리게 파헤쳐 보겠습니다!


CSS Keyframes - 한계를 부수는 복잡한 시퀀스 애니메이션의 예술

transitiontransform은 마우스 호버(:hover) 같은 단순한 트리거(Trigger)를 통해 상태 A에서 상태 B로 스르륵 변하는 단방향 애니메이션에는 최고입니다. 하지만 웹사이트에 처음 접속했을 때 텍스트가 위아래로 통통 튀며 무한히 춤을 춘다거나, 로딩 스피너가 영원히 빙글빙글 돈다거나, 배너 이미지가 0초에는 투명했다가 3초에 선명해지고 5초에 왼쪽으로 날아가버리는 한 편의 영화 같은 '시나리오'는 어떻게 만들까요? 이를 위해 CSS는 플래시(Flash)나 프리미어 프로 같은 동영상 편집기에서나 쓰던 '타임라인 프레임(Timeline Frame)' 개념을 웹 브라우저에 그대로 이식했습니다. 바로 `@keyframes``animation` 속성입니다. 자바스크립트를 단 한 줄도 쓰지 않고도 무한 반복, 일시정지, 역재생까지 모든 것을 지배하는 시퀀스 애니메이션의 세계! 이번 글에서는 CSS로 영화 감독이 되는 기적의 keyframes 문법을 상세히 알아보겠습니다.

1. 시나리오 작성: @keyframes 규칙

영화 감독이 대본을 쓰듯, @keyframes 키워드 뒤에 애니메이션의 '이름'을 짓고 중괄호 안에 시간의 흐름(0% ~ 100%)에 따른 CSS의 변화 상태를 기록합니다.

/* 'bounce-dance'라는 이름의 대본(시나리오) 작성 */
@keyframes bounce-dance {
    0% {
        transform: translateY(0); /* 시작은 제자리 */
        opacity: 1;               /* 선명하게 */
    }
    50% {
        transform: translateY(-30px); /* 중간(50%)쯤 왔을 때 위로 30px 점프! */
        opacity: 0.5;                 /* 반투명해짐 */
    }
    100% {
        transform: translateY(0); /* 끝날 땐 다시 제자리로 착지 */
        opacity: 1;               /* 다시 선명하게 복구 */
    }
}

단순히 처음(0%)과 끝(100%)만 있다면 from { ... } to { ... } 라는 영어 단어로 대체해서 적을 수도 있습니다.

2. 배우 캐스팅과 큐(Cue) 사인: animation 속성

대본(keyframes)을 다 썼다고 해서 저절로 춤을 추는 요소는 없습니다. 이제 화면의 특정 HTML 요소(배우)에게 "네가 방금 쓴 저 대본대로 연기해!"라고 지시를 내려야 합니다. 이때 animation 속성을 사용합니다.

애니메이션의 디테일 제어 파라미터들

  • animation-name: 방금 지어준 대본의 이름 (예: bounce-dance)
  • animation-duration: 한 편의 대본을 처음부터 끝까지 읽는 데 걸리는 총 시간 (예: 2s)
  • animation-iteration-count: 몇 번이나 반복 재생할 것인가? 숫자를 넣어도 되고, 영원히 반복하려면 infinite(무한 루프)를 적습니다. (로딩 스피너의 필수 키워드)
  • animation-direction: 100%까지 끝났을 때 팍! 하고 처음으로 돌아갈지, 아니면 비디오 거꾸로 감기처럼 100%에서 0%로 스무스하게 역재생(alternate)할지 결정합니다.

💡 전문가의 축약(Shorthand) 코드 한 줄

위 속성들을 전부 따로 쓰면 코드가 터져나갑니다. 실무에서는 한 줄로 압축합니다.

.ball {
    width: 50px; height: 50px; background: red; border-radius: 50%;
    
    /* 이름 | 재생시간 | 속도곡선(지연시간) | 반복횟수 | 왕복재생여부 */
    /* 💡 해석: bounce-dance 대본을 2초 동안, 부드럽게(ease-in-out), 영원히(infinite), 끝에 다다르면 거꾸로 역재생(alternate) 하면서 무한 연기하라! */
    animation: bounce-dance 2s ease-in-out infinite alternate;
}

3. 실무 최다 빈출: Skeleton UI 로딩 애니메이션

요즘 유튜브나 넷플릭스에 들어가면 데이터가 로딩되기 전에 회색 네모 박스 위로 은은한 하얀색 빛이 쓱~ 하고 스쳐 지나가는 고급스러운 로딩 화면을 본 적이 있을 겁니다. 이를 '스켈레톤(Skeleton) UI'라고 부르며, JS 로딩 상태와 순수 CSS Keyframes 애니메이션의 결합으로 만들어냅니다. (보통 배경색의 background-position을 0%에서 100%로 무한 이동시키는 대본을 짭니다.)

정리하며

@keyframes를 마스터하면 웹페이지는 더 이상 죽어있는 문서가 아니라, 시간의 흐름에 따라 살아 숨 쉬는 한 편의 모션 그래픽 캔버스가 됩니다. JS로 무거운 애니메이션 라이브러리를 끌어다 쓰기 전에, "이거 순수 CSS 애니메이션으로 짤 수 있지 않나?"라고 고민해 보는 것이 퍼포먼스 최적화의 첫걸음입니다. 자, 이제 우리는 레이아웃부터 화려한 애니메이션까지 CSS의 모든 무기를 갖추었습니다. 이제 이 무기들을 모조리 조립하여 실무 프론트엔드의 최종 보스 컴포넌트를 만들어 볼 차례입니다. 다음이번 포스팅에서는 화면 정중앙에 뜨면서 배경을 어둡게 만들고, X버튼으로 닫히는 궁극의 실무 패키지, "반응형 모달(Modal) 팝업창 완벽 구현" 프로젝트를 뼈 때리게 파헤쳐 보겠습니다!

반응형