
Cubic-Bezier 이징 — 자연스러운 CSS 애니메이션 만들기
📷 Pixabay / PexelsCubic-Bezier 이징 — 자연스러운 CSS 애니메이션 만들기
대부분의 CSS 애니메이션이 뻣뻣해 보이는 이유는 잘못된 이징 곡선 때문입니다. cubic-bezier 곡선 읽는 법, 오버슈트가 필요한 순간, 2024년 등장한 linear() 함수까지 실전 가이드.
처음 카드 애니메이션을 부드럽게 만들어 보려고 했을 때 오후 내내 transition-duration만 만지작거렸지만 진전이 없었습니다. 카드는 한 위치에서 다른 위치로 300ms에 움직였습니다. 그 다음엔 500ms. 그 다음엔 200ms. 어느 것도 자연스럽게 느껴지지 않았습니다. 기술적으로는 맞고 타이밍도 합리적이었지만, 움직임이 싸구려처럼 보였습니다 — 테이블에 카드를 놓는다기보다 와이어에 매달린 무대 소품 같은 느낌이었습니다.
문제는 시간이 아니었습니다. 이징이었습니다. 기본값 ease를 쓰고 있었는데, 출발점으로는 괜찮지만 사실 대부분의 경우 정답이 아닙니다. 해결책은 6글자 변경이었습니다. cubic-bezier(0.4, 0, 0.2, 1). 같은 애니메이션, 같은 시간인데, 갑자기 무게감이 생겼습니다.
CSS 애니메이션이 뻣뻣하거나 가짜 같아 보이는데 이유를 모르겠다면, 답은 거의 항상 이징 곡선입니다. 이 글은 cubic-bezier 곡선을 어떻게 사고하고, 어떤 것이 어떤 맥락에서 자연스러운지, 그리고 이 형식의 한계가 어디까지인지 다룹니다.
함께 쓸 도구는 CSS Cubic Bezier 생성기입니다. 제어점을 드래그하면서 곡선과 라이브 미리보기를 동시에 볼 수 있습니다. 함수 호출의 네 숫자만 보고 직관적으로 느끼는 건 사실상 불가능하기 때문에 만들었습니다.
cubic-bezier 곡선의 정체
cubic-bezier 뒤의 수학은 1960년대 르노에서 자동차 차체 디자인을 위해 곡선 형식을 개발한 프랑스 엔지니어 피에르 베지어에게서 왔습니다. 그가 정착한 형태는 두 개의 앵커 포인트(시작과 끝)와 그 사이로 곡선을 끌어당기는 두 개의 제어점을 사용합니다.
CSS에서 시작 앵커는 (0, 0)에 고정되고 끝 앵커는 (1, 1)에 고정됩니다. X축은 시간입니다 — 시작에서 0, 끝에서 1. Y축은 애니메이션되는 속성의 진행도입니다 — 0은 진행 없음, 1은 완료. 지정하는 두 제어점은 곡선을 휘게 하는 X-Y 좌표입니다.
cubic-bezier(0.4, 0, 0.2, 1)은 이렇게 말합니다.
- (0, 0)에서 시작
- 첫 번째 제어점 (0.4, 0)
- 두 번째 제어점 (0.2, 1)
- (1, 1)에서 끝
첫 번째 제어점이 곡선 아래(Y = 0)에 있어서 애니메이션 초반을 평평하게 끌어당깁니다. 두 번째 제어점이 위(Y = 1)에 있어서 후반을 평평하게 끌어당깁니다. 곡선의 가운데가 가장 가파른 기울기를 가집니다. 즉, 속성이 처음에는 천천히, 중간에는 빠르게, 끝에 다시 천천히 변합니다. 이것이 「ease-in-out」이라는 의미입니다 — 다만 이 특정 곡선은 감속 쪽에 더 무게를 두는데, 이는 Material Design의 정방향 이동 표준입니다.
수학을 외울 필요는 없습니다. 필요한 것은 곡선 모양이 인지된 움직임에 어떻게 매핑되는지에 대한 직관입니다.
곡선 모양 읽기
CSS Cubic Bezier 생성기를 열고 점을 끌어 보세요. 패턴은 놀라울 정도로 일관됩니다.
평평하게 시작해서 가파르게 끝나는 곡선 = 천천히 시작, 빠르게 마무리 = 「ease-in」. 멀어지면서 가속하는 것들. 화면을 떠나는 요소에 사용합니다 — 모달 닫기, 알림 사라짐, 휴지통으로 이동. 갈수록 빨라지는 움직임이 「떠나는 것은 가속한다」는 우리의 기대와 맞습니다.
가파르게 시작해서 평평하게 끝나는 곡선 = 빠르게 시작, 천천히 마무리 = 「ease-out」. 도착해서 안착하는 것들. 나타나는 요소에 사용합니다 — 모달 열기, 툴팁, 페이지 전환 들어옴. 빠르게 시작해서(시선을 끌고) 안착하면서 감속합니다(자리잡음). 제 경험상 UI에서 가장 자주 정답인 이징입니다.
S자 곡선 = 천천히 시작, 빠른 중간, 천천히 끝 = 「ease-in-out」. 두 가시 상태 사이의 이동. 카드 슬라이드, 페이지 섹션 재배치, 드로어 패널 이동에 씁니다. 출발과 착륙 모두 부드럽습니다.
거의 직선 대각선 = linear. 일정 속도. UI 모션에는 거의 절대 쓰지 마세요. linear는 기계적으로 느껴집니다. 현실 세계의 물체는 일정 속도로 움직이지 않으니까요. 마찰, 중력, 관성이 가속과 감속을 만듭니다. linear가 정답인 두 가지 경우는: 시작과 끝을 느낄 수 없는 연속 루프(스피너, 마퀴, 반복되는 진행 표시기), 그리고 순수 데이터 시각화(특정 숫자에 정확히 비례해 증가하는 값)입니다.
잠깐 0 아래나 1 위로 벗어나는 곡선 = 오버슈트. 요소가 앞으로 가기 전에 살짝 뒤로 당기거나, 안착 전 목표를 살짝 넘어가는 「back」 이징에 사용됩니다. 시선을 끄는 순간에 유용합니다. 다른 곳에는 남발 금지.
실제로 쓰는 곡선들
CSS 애니메이션을 충분히 작성한 뒤 정착한 작은 곡선 팔레트로 필요한 것의 90%를 커버합니다.
표준 ease-out: cubic-bezier(0.0, 0.0, 0.2, 1)
Material Design의 「감속」 곡선. 들어오는 모든 것의 기본값. 모달, 툴팁, 드롭다운 메뉴, 페이드인.
표준 ease-in: cubic-bezier(0.4, 0.0, 1, 1)
Material Design의 「가속」 곡선. 떠나는 것들. 모달 닫기, 알림 사라짐, 항목 삭제.
표준 ease-in-out: cubic-bezier(0.4, 0.0, 0.2, 1)
Material Design의 「표준」 곡선. 두 가시 상태 사이의 이동. 드로어 패널, 카드 이동, 뷰 전환.
부드러운 ease-out: cubic-bezier(0.16, 1, 0.3, 1)
표준보다 더 두드러진 감속. 더 「프리미엄」하거나 여유로운 느낌. 의도적인 움직임을 원하는 히어로 요소나 큰 전환에 사용합니다.
Anticipate: cubic-bezier(0.68, -0.55, 0.27, 1.55)
앞으로 가기 전에 살짝 뒤로 당겼다가, 안착 전 오버슈트합니다. 시선을 끄는 순간에만 아껴 사용 — 「성공」 체크 표시, 컨페티 애니메이션, 정말 눈에 띄어야 하는 토스트 알림. 사용자가 수백 번 보게 될 UI에는 절대 쓰지 마세요.
대략 이게 전부입니다. 프로젝트당 특정 브랜드 모먼트를 위한 커스텀 곡선이 한두 개 정도. 나머지는 위 중 하나를 기본값으로 두세요.
cubic-bezier의 한계
여기 cubic-bezier의 한계가 있습니다. 두 제어점을 가진 하나의 곡선입니다. 실제 모션이 하는 어떤 일들은 표현할 수 없습니다.
진짜 스프링 물리는 못 합니다. 스프링은 진동합니다 — 오버슈트하고, 돌아오고, 좀 덜 오버슈트하고, 돌아오고, 점점 안착합니다. 즉 한 번의 모션 안에 여러 번의 진동이 있습니다. cubic-bezier는 최대 한 번의 오버슈트만 가능합니다. iOS에서 보이는 통통 튀며 안착하는 애니메이션은 cubic-bezier만으로는 못 만듭니다. linear() 함수(아래 참고)나 스프링 수학을 하는 JS 애니메이션 라이브러리가 필요합니다.
다단계 모션을 표현하지 못합니다. 「반쯤 이동, 잠깐 멈춤, 그다음 계속」은 단일 cubic-bezier로 표현할 수 없습니다. 키프레임이나 체인 트랜지션이 필요합니다.
스텝이나 스냅을 못 합니다. 「25%로 점프, 유지, 50%로 점프, 유지」는 steps()의 영역입니다. cubic-bezier는 항상 연속적인 모션을 만듭니다.
네 숫자는 가독성이 없습니다. cubic-bezier(0.16, 1, 0.3, 1)을 차갑게 보여 주면 곡선을 그려낼 수 없습니다. 이 형식의 가장 큰 실용적 문제입니다. CSS Cubic Bezier 생성기 같은 도구는 정확히 이 형식을 사용 가능하게 만들기 위해 존재합니다. 시각적 미리보기 없는 숫자는 거의 의미가 없습니다.
linear() 함수 — 2024년의 게임 체인저
2023년 말부터 2024년에 걸쳐 브라우저들이 새로운 이징 함수 지원을 출시했습니다. linear(). 이름과 달리 실제로는 선형이 아닙니다. 일련의 점으로 이징 곡선을 지정하면 브라우저가 점 사이를 샘플링합니다.
문법:
transition-timing-function: linear(0, 0.05, 0.2, 0.4, 0.7, 0.9, 1);
각 숫자는 0에서 1까지 균등 간격의 X 위치에 있는 Y 값입니다. 브라우저는 각 점 사이를 선형으로 보간합니다 — 다만 점이 충분히 많으면 어떤 곡선이든 근사할 수 있습니다. 여러 번 진동하는 스프링 물리도 포함입니다.
Linear Easing Generator(Jake Archibald의 오픈소스 프로젝트) 같은 도구는 스프링 곡선을 그리거나 Framer Motion 스프링 설정을 가져와서 원하는 만큼의 샘플 점이 있는 linear() 값을 내보낼 수 있게 해 줍니다. CSS 애니메이션에 진정으로 혁명적입니다 — 처음으로 자바스크립트 없이 순수 CSS로 진짜 스프링 모션을 할 수 있습니다.
cubic-bezier()보다 linear()로 손이 가야 할 때:
- 스프링 모션(여러 번 튀며 안착)을 원할 때
- JSON이나 SVG 경로 데이터를 내보내는 디자인 도구의 곡선과 정확히 일치시키고 싶을 때
- 단일 트랜지션 안에서 이징 단계를 체인으로 연결하고 싶을 때
cubic-bezier()를 고수해야 할 때:
- 모션이 단순하고 일회성일 때
- 6개월 후 다시 봤을 때 CSS가 읽히기를 원할 때
- cubic-bezier 곡선을 사용하는 기존 디자인 시스템과 매칭할 때
저는 여전히 UI 애니메이션의 95%에 cubic-bezier를 기본값으로 둡니다. linear()가 정답인 경우는 실재하지만 특정한 경우입니다.
steps와 그 외 이징 함수들
CSS에는 cubic-bezier와 linear 외에 알아 둘 만한 이징 옵션이 몇 개 더 있습니다.
steps(n, jump-start | jump-end) — 부드럽게 애니메이션하는 대신 이산 값으로 스냅합니다. 스프라이트 시트 애니메이션, 분할된 진행 표시줄, 프레임 단위 애니메이션에 유용합니다. steps(8, end)는 시간 동안 8번의 이산 점프로 진행합니다.
step-start와 step-end — 시작이나 끝에서 즉시 값을 점프합니다. 시간 단위와 조율하고 싶은 즉각적인 상태 변화에 유용합니다.
ease, ease-in, ease-out, ease-in-out — 특정 cubic-bezier 값에 매핑되는 이름 키워드. 편리한 기본값이지만 실제 곡선은 평범합니다 — ease는 cubic-bezier(0.25, 0.1, 0.25, 1)인데, 괜찮긴 해도 Material Design 곡선만큼은 아닙니다. cubic-bezier를 직접 쓰는 게 익숙해지면 이 이름 키워드는 거의 안 씁니다.
애니메이션의 절반은 곡선
곡선을 골랐다면 CSS 애니메이션 생성기가 다음 단계로 유용합니다 — 곡선이 적용된 전체 키프레임 애니메이션과 복사할 수 있는 CSS를 줍니다. 이징보다 키프레임이 더 중요한 복잡한 다단계 애니메이션에는 그 도구가 더 유용합니다.
색상 트랜지션이 포함된 애니메이션이라면 CSS 그라디언트 생성기로 보간된 background-color 애니메이션이 어떻게 보일지 미리 볼 수 있습니다 — 그라디언트는 트랜지션과 같은 색상 보간을 사용합니다.
경로 기반 애니메이션과 클립 노출은 CSS Clip Path 생성기가 기하 측면을 다룹니다. clip-path 애니메이션과 잘 고른 cubic-bezier를 결합하면 놀라울 정도로 세련된 노출 효과를 만들 수 있습니다.
흔히 보는 함정
「단순해 보여서」 모든 곳에 linear 쓰기. linear는 최악의 기본값입니다. linear를 고르기 전에 이름 이징 중 아무거나 골라 보세요(ease-out이 안전한 선택).
기본 transition: all로 자기 자신을 놀라게 하기. 단축형 transition: all 300ms는 변하는 모든 속성을 애니메이션합니다. 의도하지 않은 것까지(높이 변화, 색상 변화 등). 명시적으로 쓰세요. transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1)은 transform만 애니메이션합니다.
평범한 트랜지션에 오버슈트 이징 사용. 버튼 색상 변화에 back 이징은 우스꽝스럽습니다. 오버슈트는 축하나 시선 끌기 모먼트에 아껴 두세요.
시간과 곡선의 상호작용 잊기. 100ms ease-out과 500ms ease-out은 다르게 느껴집니다. 같은 곡선도 시간이 다르면 다른 애니메이션으로 읽힙니다. 둘을 함께 튜닝하세요.
cubic-bezier로 레이아웃 속성 애니메이션하고 왜 끊기나 의아해하기. cubic-bezier는 무엇을 애니메이션할지가 아니라 어떻게 이징할지만 바꿉니다. width나 height를 애니메이션하면 레이아웃 리플로우가 발생해 이징과 무관하게 끊깁니다. 대신 transform: scale()이나 transform: translate()를 써 보세요 — GPU 합성되어 부드럽게 유지됩니다.
디자이너와 개발자가 다른 곡선 사용. 디자인 시스템이 이징 토큰을 명세한다면 Figma와 CSS 둘 다 그것을 사용해야 합니다. 목업과 구현 사이의 가장 흔한 시각적 불일치는 일치하지 않는 이징 곡선입니다. 표준화하세요.
접근성 — prefers-reduced-motion
일부 사용자는 애니메이션에서 메스꺼움, 어지러움, 두통을 겪습니다. 브라우저는 이 환경설정을 미디어 쿼리로 노출합니다.
@media (prefers-reduced-motion: reduce) {
* {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
위의 대대적 접근은 모든 애니메이션을 사실상 즉각적으로 줄입니다. 더 우아한 접근은 극적인 이징을 단순한 것으로 바꾸고, 오버슈트 애니메이션을 완전히 제거하고, 시간을 단축하는 것입니다.
@media (prefers-reduced-motion: reduce) {
.modal {
transition: opacity 100ms linear;
}
}
prefers-reduced-motion 미디어 쿼리는 현재 모든 브라우저에서 지원됩니다. 존중하지 않을 변명이 없습니다. 모든 프로젝트에서 확인합니다 — 10분 걸리고 필요한 사람들의 경험을 의미 있게 개선합니다.
이징 어휘 만들기
애니메이션 이징은 기술 명세가 아니라 어휘입니다. 「이 곡선 = ease-out, 진입에 사용」, 「이 곡선 = ease-in, 퇴장에 사용」을 내재화하면 cubic-bezier 숫자는 배경으로 사라지고 의도의 관점에서 모션을 사고하기 시작합니다.
CSS Cubic Bezier 생성기는 그 어휘를 가장 빠르게 쌓는 방법입니다. 점을 끌어 보세요. 두 곡선을 나란히 비교하세요. 같은 곡선을 100ms, 500ms, 1000ms에서 시도해 보세요. 자연스럽게 느껴지는 것을 저장하고 재사용하세요.
toolboxhubs.com의 애니메이션은 약 6개의 큐레이션된 곡선 세트를 사용합니다. 모든 트랜지션이 그 세트에서 가져옵니다. 결과는 사이트 전체의 일관된 느낌입니다 — 다양한 요소들이 조율된 방식으로 함께 움직이는 것이죠. 이 조율이 다듬어진 인터페이스와 그렇지 않은 것의 차이입니다. 그리고 그것은 신뢰할 수 있는 곡선을 고르는 데서 시작합니다.