Motion DesignP4 본문

모션 디자인

흐름을 명확하게 하는 절제된 모션 — compositor 친화 속성, 지속 시간·이징 체계, 스프링(공간·효과) 물리, 로딩·전환 패턴, AI Thinking, 그리고 prefers-reduced-motion 대응을 정의합니다.

마지막 업데이트 2026-06-15

모션 원칙#

모션은 장식이 아니라 흐름을 설명하는 언어입니다. WDS의 모션은 네 가지 규칙을 따릅니다.

  • 목적이 있을 때만 — 상태 변화·공간 관계·연속성을 전달할 때만 움직입니다. 이유 없는 애니메이션은 넣지 않습니다.
  • compositor 친화 속성만transform·opacity만 전환합니다. width·height·top·margin 등 레이아웃을 흔드는 속성은 애니메이션하지 않습니다(리플로우·재깅 방지).
  • 빠르고 절제되게 — 대부분의 전환은 150–250ms입니다. 길고 화려한 모션은 작업을 방해합니다.
  • 언제나 끌 수 있게prefers-reduced-motion을 전면 존중합니다. 모션이 정보를 전달한다면, 모션을 끈 사용자에게는 정적 대체를 제공합니다.

지속 시간#

지속 시간은 세 단계 토큰으로 고정합니다 — 화면마다 다른 값을 쓰지 않습니다.

아래 플레이어로 duration·ease·spring 토큰을 직접 재생해 보세요 — specimen이 실제로 움직이고, 같은 진행도로 곡선 위 플레이헤드가 달립니다. "모션 감소"를 켜면 모션을 끈 사용자에게 제공하는 정적 대체(최종 상태 + 곡선 + 수치)를 그 자리에서 미리볼 수 있습니다.

라이브 모션
ease.standard기본 양방향 전환cubic-bezier(0.4, 0, 0.2, 1) · 400ms0 / 400ms
토큰용도
duration.fasthover·focus·active 등 즉각 피드백, 작은 요소 토글
duration.normal패널·드롭다운·모달 등장/퇴장, 탭 전환
duration.slow큰 표면(시트·드로어·페이지) 전환, 강조 연출

이징은 다섯 가지를 씁니다. 기본 전환·짧은 등장에는 ease.out(빠르게 시작해 부드럽게 감속), 양방향 연속 전환에는 ease.standard를 씁니다. 강조 모션이 필요한 긴 거리·강한 표현에는 M3 emphasized 계열을 씁니다 — ease.emphasized는 양방향 강조 전환, ease.emphasized-decelerate는 화면 안으로 들어오며 감속하는 등장, ease.emphasized-accelerate는 화면 밖으로 나가며 가속하는 퇴장에 씁니다.

토큰설명
M3 emphasized — 화면 전환·강조 모션의 기본 (CSS 근사)
M3 emphasized decelerate — 표면 등장(enter). 오버레이 패널 진입에 사용
M3 emphasized accelerate — 표면 퇴장(exit)

상태 전환#

hover·focus·active는 duration.fast로 즉시 반응합니다. 색·그림자·약간의 transform만 바꿔 손맛을 주되, 레이아웃은 고정합니다. 아래 버튼에 마우스를 올리고 키보드로 포커스해 보세요.

hover·focus·active — 즉각적이고 절제된 피드백

포커스 링은 모션이 아니라 가시성이 핵심입니다 — 자세한 규칙은 접근성에서 다룹니다.

표면 전환#

떠 있는 표면은 등장 방향과 속도로 공간 관계를 전달합니다.

표면모션지속
드롭다운·툴팁·팝오버opacity + 4–8px translateYduration.fast
모달·다이얼로그opacity + 살짝 scale(0.98→1)duration.normal
바텀시트·드로어·사이드시트가장자리에서 슬라이드 인duration.slow

방향은 의미를 가집니다 — 바텀시트는 아래에서, 사이드시트는 옆에서 들어와 "어디서 왔는지"를 남깁니다. 오버레이(모달·드로어·바텀시트·팝오버) 등장에는 ease.emphasized-decelerate를 써서 화면 안으로 들어오며 감속하는 "안착감"을 줍니다. 닫힘은 같은 경로를 ease.emphasized-accelerate로 역재생하며, 빠르게 가속해 화면을 벗어납니다.

표면이 위치·크기로 움직일 때(모달의 살짝 scale, 시트의 슬라이드)는 cubic-bezier 대신 아래 스프링 절의 공간 spring 토큰(spring.spatial.*)을 써서 약간의 오버슈트로 "물리적으로 안착하는" 손맛을 줄 수 있습니다. 표면의 색·불투명도·blur 전환에는 오버슈트 없는 효과 spring(spring.effect.*)을 씁니다.

스프링(Spring)#

cubic-bezier 이징이 "정해진 곡선을 시간에 맞춰 따라가는" 모션이라면, 스프링은 질량·강성·감쇠 라는 물리에서 곡선이 자연스럽게 도출되는 모션입니다. 절제 원칙은 그대로입니다 — 스프링은 화려한 연출이 아니라, 표면이 "물리적으로 안착한다"는 손맛을 더하는 강조 모션에만 씁니다.

WDS 스프링은 두 갈래로 나뉩니다. 무엇이 움직이는가에 따라 다른 물리를 씁니다.

  • 공간(spatial) — 위치·크기·레이아웃이 움직이는 모션. 약간 과소감쇠(damping ratio 0.8)라 목표를 살짝 지나쳤다가 돌아오는 미세한 오버슈트가 있습니다. 이 오버슈트가 생동감(personality)입니다.
  • 효과(effect) — 색·불투명도·blur가 변하는 모션. 임계감쇠(damping ratio 1.0)라 오버슈트 없이 가장 빠르게 안착합니다. 색이 출렁이면 잔상처럼 보여 가독을 해치므로 오버슈트를 두지 않습니다.
스프링 물리공간 ζ0.8은 목표를 살짝 지나쳤다 안착(오버슈트=personality), 효과 ζ1.0은 넘지 않음.
변위 — 목표(1.0)까지 안착
변위 − 목표 · 안착 구간 · y축 ±2%로 확대
spatial · k380 · ζ0.8 · 372ms · 오버슈트 ~1.5%effect · k1600 · ζ1.0 · 188ms · 오버슈트 없음아래 패널은 안착 후반부를 잘라 편차(변위−목표)를 ±2%로 확대한 표기 — 실제 오버슈트는 변위의 약 1.5%입니다(위 패널의 곡선 모양이 실제 비율).

각 스프링은 빌드 타임에 CSS linear() 이징 함수정착 시간(duration) 두 변수로 출력됩니다. 질량-스프링-감쇠 방정식의 닫힌 해를 32점 샘플링해 linear() 점열을 만들고, 변위가 목표의 0.5% 이내로 안착하는 시각을 duration으로 잡습니다. 웹은 var(--wds-spring-*) 이징과 var(--wds-spring-*-duration)transition에 함께 쓰고, Flutter는 동일 토큰에서 생성된 WizSpringsSpringDescription을 씁니다 — 웹과 Flutter가 같은 물리에서 나옵니다.

토큰종류강성·감쇠비정착(ms)용처
spring.spatial.default공간k380 · ζ0.8372ms위치·크기·레이아웃 전환 기본
spring.spatial.fast공간k800 · ζ0.8256ms작은 요소·즉각 반응
spring.spatial.slow공간k200 · ζ0.8512ms큰 표면·강조 등장 전용 — 작은 컨트롤·고빈도 전환엔 쓰지 말 것
spring.effect.default효과k1600 · ζ1.0188ms색·불투명도·blur 전환 기본
spring.effect.fast효과k3800 · ζ1.0124ms즉각 피드백의 색·불투명도

강성(k)이 높을수록 빠르게 안착합니다. 감쇠비(ζ)는 오버슈트의 유무를 정합니다 — 공간 스프링의 0.8은 절제된 오버슈트, 효과 스프링의 1.0은 오버슈트 없음입니다. 값은 Material 3 Expressive의 spring 모션 토큰을 기준으로 큐레이션했습니다. 정착 시간은 빌드 타임 결정값입니다 — 토큰 빌드가 linear()·--wds-spring-*-duration에 같은 ms 값을 박으므로 연쇄(choreography) 타이밍에 그대로 쓸 수 있습니다.

/* 공간 spring — 카드가 살짝 오버슈트하며 안착 (transform만 — 레이아웃 무흔들) */
.card {
  transition:
    transform var(--wds-spring-spatial-default-duration) var(--wds-spring-spatial-default);
}

/* 효과 spring — 색·불투명도는 오버슈트 없이 안착 */
.surface {
  transition:
    opacity var(--wds-spring-effect-default-duration) var(--wds-spring-effect-default);
}

스프링도 레이아웃 속성은 애니메이션하지 않습니다(transform·opacity·clip-path만). 그리고 prefers-reduced-motion에서는 다른 모션과 동일하게 전환을 제거하고 즉시 표시합니다(아래 표 참조). Flutter에서는 SpringDescription이 물리 상수일 뿐이므로, 스프링 구동 애니메이션은 MediaQuery.disableAnimationsOf(context)로 감싸 reduce 시 최종 값으로 즉시 스냅해야 합니다 (웹 전역 가드와 같은 정신 — 기존 wiz_modal·wiz_toast 패턴 계승).

로딩#

대기 시간의 성격에 따라 표현을 고릅니다.

  • Spinner — 길이를 모르는 짧은 대기. 행동 옆이나 영역 중앙에 둡니다.
  • Skeleton — 곧 들어올 콘텐츠의 형태를 미리 보여 줄 때(레이아웃 점프 방지).
  • Progress — 진행률을 아는 작업(업로드 등)은 결정 모드, 모르면 indeterminate.
Spinner · Skeleton · Progress

AI Thinking#

AI 상태는 모션으로 "지금 무엇을 하는지"를 전달합니다 — 멈춘 화면은 멈춘 시스템처럼 보입니다.

  • Thinking — 추론 중. 은은한 펄스/도트로 "살아 있음"을 표시하되 진행률을 약속하지 않습니다.
  • Streaming — 응답 생성 중. 텍스트가 흐르듯 나타나고 커서가 깜빡입니다.
  • Tool Calling — 외부 도구 호출. 도구 라벨과 함께 indeterminate 표시.

상태 정의와 표현의 정본은 AI 가이드라인의 AI State Model 9종입니다. 모든 AI Thinking 모션도 prefers-reduced-motion에서는 정적 라벨로 대체합니다.

Reduced Motion 대응#

prefers-reduced-motion: reduce를 켠 사용자에게는 모션을 제거하되 정보는 유지합니다 — 애니메이션이 유일한 단서가 되어선 안 됩니다.

모션reduce 시 대체
등장/퇴장 전환전환 제거, 즉시 표시/숨김
스프링 전환(공간·효과)spring 전환 제거, 즉시 최종 상태로 표시(오버슈트 없음)
Spinner 회전회전 정지 + "로딩 중" 텍스트 유지
Progress indeterminate 왕복왕복 정지 + 라벨 유지
Skeleton shimmershimmer 제거, 정적 플레이스홀더 유지
AI Thinking 펄스펄스 제거, 상태 라벨 텍스트 유지

스프링을 포함한 CSS 전환은 WDS 전역 베이스(tokens/wds-base.css.wds-root 스코프)가 prefers-reduced-motion에서 transition·animation 지속 시간을 ~0으로 강제하므로 자동으로 최종 상태에 스냅됩니다 — 오버슈트가 새지 않습니다. 컴포넌트 CSS의 @media (prefers-reduced-motion: reduce) 블록은 JS 구동·비-transition 모션을 위한 규약으로 남기며, 신규 모션을 추가할 때 이 대체를 함께 정의합니다.