모션과 전환
움직임으로 관계와 변화를 설명하는 법과, 지나친 움직임을 줄이는 법이다.
목차 (12)
모션이 하는 일
화면의 움직임은 장식이 아니라 설명이다. 상태가 한순간에 바뀌면 사람은 앞뒤 화면을 비교해 무엇이 바뀌었는지 스스로 알아내야 한다. 움직임은 그 사이를 이어 "무엇이 어디서 와서 어디로 갔는지"를 보여 준다. 쓰임은 크게 넷이다.
- 관계: 목록의 카드가 그대로 커져 상세 화면이 되면, 둘이 같은 것이라는 사실이 설명 없이 전해진다.
- 방향: 다음 단계는 오른쪽에서 들어오고 뒤로 가면 오른쪽으로 나간다. 사람은 화면들이 어떤 공간에 놓여 있다고 느끼고 길을 잃지 않는다.
- 피드백: 누른 자리의 작은 변화가 입력을 받았다고 알린다. 이것이 인터랙션과 상태 노트의 "즉시" 피드백이다.
- 주의: 새 알림 배지가 한 번 흔들리면 눈이 간다. 계속 흔들리면 방해가 되므로 한 번으로 끝낸다.
이 넷 가운데 무엇도 하지 않는 움직임은 지운다. 매번 보는 화면의 화려한 전환은 처음 한두 번만 즐겁고, 그 뒤로는 기다리는 시간이 된다.
이징 곡선: 언제 빠르고 언제 느린가
이징(easing)은 시간에 따라 움직임이 얼마나 진행했는지를 정하는 곡선이다. 현실의 물체는 정지 상태에서 갑자기 최고 속도가 되지 않는다. 일정한 속도(linear)로 움직이는 화면 요소가 기계적으로 보이는 이유다. CSS의 키워드는 모두 3차 베지어 곡선의 줄임말이다. MDN에 따르면 ease는 cubic-bezier(0.25, 0.1, 0.25, 1), ease-in은 (0.42, 0, 1, 1), ease-out은 (0, 0, 0.58, 1), ease-in-out은 (0.42, 0, 0.58, 1)이다.
- 들어올 때는 감속(ease-out 계열): 빠르게 나타나 천천히 자리 잡는다. 사용자는 곧바로 반응을 보고, 끝부분에서 어디에 멈췄는지 읽는다.
- 나갈 때는 가속(ease-in 계열): 천천히 출발해 빠르게 사라진다. 이미 떠나는 것을 오래 볼 이유가 없다.
- 화면 안에서 옮길 때는 양쪽(ease-in-out 계열): 출발과 도착이 모두 화면 안에서 보이기 때문이다.
Material 3의 이징 토큰도 같은 생각이다. standard는 (0.2, 0, 0, 1), 들어올 때 쓰는 emphasized decelerate는 (0.05, 0.7, 0.1, 1), 나갈 때 쓰는 emphasized accelerate는 (0.3, 0, 0.8, 0.15)다. 두 곡선을 이어 붙인 emphasized는 한 개의 베지어가 아니라 경로로 정의되어 있다.
0ms: linear 0%, ease-out 0%, ease-in 0%.
/* 들어올 때: 빠르게 와서 천천히 멈춘다 */
.sheet-enter {
transition: transform 300ms cubic-bezier(0.05, 0.7, 0.1, 1);
}
/* 나갈 때: 천천히 출발해 빠르게 사라진다, 들어올 때보다 짧게 */
.sheet-exit {
transition: transform 200ms cubic-bezier(0.3, 0, 0.8, 0.15);
}스프링: 시간 대신 물리로
베지어 이징은 지속 시간을 먼저 정한다. 스프링은 반대로 강성(stiffness)과 감쇠비(damping ratio)만 정하고, 움직임이 끝나는 시간은 물리 법칙이 정한다. 손가락으로 끌다 놓은 시트처럼 움직이는 도중에 목표가 바뀌거나 처음 속도가 제각각인 경우에 자연스럽다. 감쇠비가 1이면 넘치지 않고 다가가고, 1보다 작을수록 목표를 넘었다가 출렁이며 돌아온다.
Material 3는 스프링도 토큰으로 둔다. 위치·크기처럼 공간을 바꾸는 spatial 스프링은 감쇠비 0.9(기본 강성 700), 색·투명도처럼 넘치면 안 되는 effects 스프링은 감쇠비 1(기본 강성 1600)이다. 문서의 설명대로 배경의 투명도가 100%를 넘어 출렁일 수는 없기 때문이다. 감쇠비 0.9의 넘침은 계산하면 약 0.15%라 "살아 있는 느낌"만 남고, 0.5처럼 16%나 넘치는 탄성은 게임이나 장난스러운 브랜드가 아니면 과하다.
지속 시간: 짧을수록 좋지만 0은 아니다
너무 빠르면 움직임을 알아채지 못하고, 너무 느리면 기다리게 한다. NN/g는 대부분의 UI 애니메이션을 복잡도와 이동 거리에 따라 100–500ms 안에 두라고 한다. 체크박스나 토글 같은 단순한 피드백은 약 100ms면 즉각적으로 느껴지고, 모달이 나타나는 것 같은 큰 화면 변화는 200–300ms, 큰 화면을 가로지르는 이동은 400ms까지 쓴다. 500ms쯤부터는 질질 끄는 느낌이 든다.
- 크기와 거리에 비례: 작은 아이콘의 색 변화는 short(50–200ms), 카드 펼치기는 medium(250–400ms), 화면 전체 전환은 long(450–600ms) 쪽이다. 토큰 값은 Material Components 문서의 표에 있다.
- 들어올 때가 조금 길다: NN/g의 예처럼 팝업은 나타날 때 300ms, 사라질 때 200–250ms로 둔다.
- 자주 볼수록 짧게: 하루에 수백 번 여닫는 메뉴는 처음 보는 온보딩 화면보다 짧아야 한다.
- 토큰으로 고정한다: 지속 시간과 이징을 화면마다 새로 고르면 제품 전체의 리듬이 흩어진다. 몇 개의 이름(짧음·보통·김)으로 묶어 디자인 시스템에 둔다(디자인 시스템 노트).
전환 패턴: 공유 요소, 컨테이너 변형, 공유 축, 페이드 스루
화면이 바뀔 때의 움직임은 두 화면의 관계에 맞춰 고른다. Material의 전환 패턴은 넷이다. 컨테이너 변형은 카드가 상세 화면으로, 떠 있는 버튼이 화면으로 커지는 것처럼 테두리 있는 요소 사이의 전환이다. 공유 축은 단계의 앞뒤나 탭처럼 공간·탐색 관계가 있는 화면을 같은 축(가로·세로·깊이)으로 함께 민다. 페이드 스루는 하단 탭을 바꿀 때처럼 서로 관계없는 화면에서 앞 화면이 사라진 뒤 새 화면이 나타난다. 페이드는 대화상자·메뉴·스낵바처럼 화면 안에서 나타나고 사라지는 요소에 쓴다.
컨테이너 변형은 공유 요소 전환(shared element transition)의 대표적인 형태다. 앞 화면의 요소와 뒤 화면의 요소를 같은 것으로 짝지어, 위치와 크기를 이어 움직인다. 안의 내용은 앞 화면 것이 먼저 흐려지고 뒤 화면 것이 나중에 나타나서, 두 내용이 겹쳐 지저분해 보이지 않는다.
0ms: 목록 속 카드. 누르면 이 카드 자체가 상세 화면이 된다.
웹에서는 View Transition API가 이것을 맡는다. document.startViewTransition()에 화면을 바꾸는 함수를 넘기면 브라우저가 앞뒤 화면을 찍어 이어 주고, 앞뒤 화면의 요소에 같은 view-transition-name을 주면 그 요소가 따로 이어 움직인다. 여러 페이지로 된 사이트는 두 문서 모두 @view-transition으로 참여를 밝힌다.
// 같은 이름을 붙인 요소끼리 앞뒤 화면에서 이어 움직인다
card.style.viewTransitionName = 'trip-hero';
document.startViewTransition(() => showDetail(trip));애니메이션 원칙 가운데 UI에 쓰는 것
디즈니 애니메이터 프랭크 토머스와 올리 존스턴은 1981년 책 『The Illusion of Life』에 캐릭터 애니메이션의 12원칙을 정리했다(위키백과 정리). 캐릭터를 위한 원칙이지만 몇 가지는 화면 요소에도 그대로 쓴다.
| 원칙 | 원래 뜻 | UI에서 |
|---|---|---|
| 느리게 시작·끝내기(slow in and slow out) | 동작의 처음과 끝에 그림을 더 많이 둬 가속·감속을 흉내 낸다 | 이징 곡선 그 자체 |
| 예비 동작(anticipation) | 점프 전에 무릎을 굽히듯 동작을 미리 알린다 | 당겨서 새로 고침 전에 화살표가 돌기 시작, 버튼이 눌리며 살짝 작아짐 |
| 따라오는 동작(follow through) | 몸이 멈춰도 느슨한 부분은 조금 더 움직인다 | 멈출 때 살짝 넘었다 돌아오는 스프링 |
| 무대 연출(staging) | 가장 중요한 것에 시선이 가게 배치·조명을 정한다 | 모달 뒤 배경을 어둡게, 한 번에 한 가지만 움직이기 |
| 호(arc) | 자연스러운 움직임은 곡선 경로를 따른다 | 장바구니로 날아가는 상품 아이콘 |
| 찌그러짐과 늘어남(squash and stretch) | 부피는 그대로 두고 모양을 바꿔 무게와 탄성을 보인다 | 게임·장난스러운 UI에서 눌림과 튕김을 과장할 때 |
나머지 원칙 가운데 타이밍(그림 수, 곧 지속 시간이 무게와 분위기를 정한다)과 과장(현실보다 조금 크게)은 위 절들에 이미 들어 있다. UI는 캐릭터보다 절제한다. 과장이 지나치면 정보보다 움직임이 먼저 보인다.
연쇄(stagger): 차례로 들어오기
목록 항목을 한꺼번에 나타내지 않고 조금씩 늦춰 차례로 들어오게 하는 것을 연쇄(stagger)라고 한다. 눈이 위에서 아래로 따라가며 읽는 순서와 묶음이 드러난다. 위의 따라오는 동작(겹치는 동작)을 여러 요소로 나눈 셈이다.
- 전체 시간에 상한을 둔다: 간격 × (항목 수 − 1) + 한 항목 시간이 전체다. 항목이 늘면 간격을 줄인다. 예에서 12개를 500ms에 맞추려면 간격이 27.3ms가 된다.
- 보이는 것만: 화면 밖 항목까지 연쇄에 넣으면 스크롤했을 때 이미 지난 애니메이션을 기다리게 된다. 첫 화면의 몇 개에만 쓴다.
- 처음 한 번만: 목록을 다시 정렬하거나 새로 고칠 때마다 연쇄를 다시 틀면 내용을 가린다.
성능: transform과 opacity, 프레임 예산
매끄러운 움직임은 매 프레임이 제때 나와야 가능하다. web.dev에 따르면 60Hz 화면에서 브라우저는 프레임 하나를 16.7ms 안에 만들어야 하고, 브라우저 자신의 일이 있어 페이지의 일은 약 10ms 안에 끝내야 한다. 120Hz 화면이면 프레임 하나가 8.3ms다. 모바일 앱의 프레임 예산은 모바일 개발 노트에 있다.
- transform과 opacity만 움직인다: web.dev의 애니메이션 안내는 애니메이션을 이 둘로 제한하라고 한다.
width·top·left를 움직이면 매 프레임 레이아웃과 페인트를 다시 한다. 위치는translate, 크기는scale로 바꾼다. - will-change는 아껴 쓴다: 같은 안내는 성능 문제가 실제로 생겼을 때만 쓰라고 한다. 미리 잔뜩 붙이면 레이어가 늘어 다른 문제가 생긴다.
- 메인 스레드를 비운다: 애니메이션 도중에 무거운 스크립트가 돌면 예산을 넘겨 프레임을 건너뛴다(끊김, jank). 데이터 처리는 애니메이션이 끝난 뒤나 다른 스레드로 미룬다.
움직임 줄이기: prefers-reduced-motion과 전정 장애
몸의 균형을 맡는 전정 기관에 장애가 있는 사람에게 화면의 움직임은 실제 증상을 일으킨다. W3C의 설명에 따르면 꼭 필요하지 않은 움직임이 어지럼, 메스꺼움, 두통을 부르고, 심하면 누워서 쉬어야 할 정도다. MDN은 큰 물체를 확대하거나 패닝하는 움직임을 대표적인 방아쇠로 든다.
운영체제는 움직임 줄이기 설정을 둔다. iOS는 설정 › 손쉬운 사용 › 동작, 안드로이드 9 이상은 설정 › 접근성 › 애니메이션 삭제, Windows 11은 설정 › 접근성 › 시각 효과 › 애니메이션 효과다. 웹은 이 설정을 prefers-reduced-motion 미디어 쿼리로 읽는다. 값은 no-preference와 reduce 둘이다.
@media (prefers-reduced-motion: reduce) {
/* 확대·이동 대신 제자리 페이드 */
.page-enter {
animation: fade-in 200ms ease-out both;
}
.parallax {
transform: none;
}
}- 지우지 말고 바꾼다: 움직임이 상태 변화를 알려 주던 것이라면 그 정보는 남겨야 한다. 이동·확대를 짧은 페이드로 바꾸는 것이 MDN 예제의 방식이다.
- 기준: WCAG 2.3.3(상호작용에서 나온 애니메이션, AAA)은 상호작용이 부른 움직임을 끌 수 있게 하라고 하고, 2.2.2(일시 정지·정지·숨기기, A)는 저절로 시작해 5초 넘게 이어지는 움직임에 멈출 방법을 두라고 한다. 2.3.1(세 번 깜빡임, A)은 1초에 세 번 넘게 번쩍이는 것을 금한다.
- 설정 화면에도 둔다: 운영체제 설정을 모르는 사람도 있으므로, 앱 안에 "애니메이션 줄이기" 스위치를 두면 더 많은 사람이 쓴다. 게임 접근성은 게임 UX 노트에 있다.
게임 UI의 트윈
게임은 UI 모션을 더 과감하게 쓴다. 대신 원리는 같다. 값이 바뀐 사실을 숨기지 않고, 그 변화를 읽을 시간을 준다. 게임 엔진에서는 값을 시작점에서 끝점까지 시간에 따라 채워 넣는 것을 트윈(tween, between에서 나온 말)이라 부른다. Godot의 Tween은 곡선 모양(전이 종류: 선형, 사인, 2·3·4·5제곱, 지수, 원, 탄성, 튕김, 뒤로 물러남, 스프링)과 곡선을 어디에 적용할지(이징 종류: 천천히 시작, 천천히 끝, 양 끝이 느림, 양 끝이 빠름)를 따로 고른다. CSS의 ease-in·ease-out과 같은 생각이다.
# 맞은 순간: 앞 막대는 바로, 잔상 막대는 0.3초 뒤 0.4초 동안
front_bar.value = hp
var tween = create_tween()
tween.tween_interval(0.3)
tween.tween_property(ghost_bar, "value", hp, 0.4) \
.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)- 정확한 값은 즉시: 체력·탄약처럼 판단에 쓰는 숫자는 바로 바꾸고, 움직임은 그 옆의 보조 정보(잔상, 숫자 튀어 오름)에 맡긴다. 트윈이 끝날 때까지 실제 값을 모르면 조작이 늦어진다.
- 획득과 손실을 다르게: 점수가 오를 때는 숫자가 빠르게 올라가는 카운트업, 잃을 때는 잔상과 흔들림처럼 성격이 다른 움직임으로 구분한다.
- 시간 배율을 따로: 게임을 일시 정지하거나 느리게 하는 동안에도 메뉴 UI는 움직여야 한다. UI 트윈은 게임 시간과 분리된 시계로 돌린다.
피격 반응, 화면 흔들림 같은 "손맛"은 게임 UX 노트에서, HUD 정보 배치는 게임 UI 노트에서 다룬다.
흔한 오해
- "애니메이션은 꾸밈이라 나중에 넣는다" — 관계와 방향을 설명하는 움직임은 정보 설계의 일부다. 무엇을 움직일지는 화면 흐름을 정할 때 함께 정한다.
- "느리게 움직여야 부드러워 보인다" — 부드러움은 이징과 프레임이 만든다. 길이는 대부분 100–500ms 안이고, 길수록 기다림이 된다.
- "linear가 가장 정직한 움직임이다" — 현실의 물체는 가속하고 감속한다. 진행 막대처럼 값 자체를 보여 줄 때가 아니면 기계적으로 보인다.
- "움직임 줄이기는 애니메이션을 모두 끄는 것" — 상태 변화의 정보는 남기고 이동·확대를 페이드 같은 덜 자극적인 것으로 바꾸는 것이다.
- "left·top으로 움직여도 요즘 기기는 빠르다" — 매 프레임 레이아웃을 다시 하게 되고, 저사양 기기나 120Hz 화면에서 가장 먼저 끊긴다. transform을 쓴다.
- "스프링은 늘 튕긴다" — 감쇠비가 1이면 넘치지 않는다. Material의 공간 스프링도 감쇠비 0.9로 거의 넘치지 않는다.
핵심 자료
요지는 직접 정리한 한 줄이다.
| 자료 | 한 줄 요지 |
|---|---|
| Material Components — Motion | 지속 시간 토큰 16개, 이징 곡선, 스프링 토큰, 전환 패턴 넷의 값과 쓰임. |
| NN/g — Animation Duration | 대부분 100–500ms. 단순한 피드백은 약 100ms, 큰 변화는 200–300ms. |
| MDN — <easing-function> | ease·ease-in·ease-out의 cubic-bezier 값과 linear()·steps(). |
| web.dev — Rendering performance | 픽셀 파이프라인 다섯 단계와 60fps 프레임 예산(약 10ms). |
| MDN — prefers-reduced-motion | 운영체제의 움직임 줄이기 설정을 CSS에서 읽는 법과 전정 장애 주의. |
| W3C — Animation from Interactions | 상호작용이 부른 움직임을 끌 수 있어야 한다는 AAA 기준과 그 이유. |
| Godot — Tween | 게임 UI 값을 트윈하는 API. 전이 종류와 이징 종류를 따로 고른다. |
- Motion — Material Design 3
- Motion (theming) — Material Components for Android
- Executing UX Animations: Duration and Motion Characteristics — NN/g
- <easing-function> — MDN Web Docs
- View Transition API — MDN Web Docs
- Rendering performance — web.dev
- How to create high-performance CSS animations — web.dev
- prefers-reduced-motion — MDN Web Docs
- Understanding SC 2.3.3 Animation from Interactions — W3C
- Understanding SC 2.2.2 Pause, Stop, Hide — W3C
- Understanding SC 2.3.1 Three Flashes or Below Threshold — W3C
- Twelve basic principles of animation — Wikipedia
- Tween — Godot Engine documentation