Dynamization Core(한국어)
SKILL.md의 한국어판입니다. 영어 정본은 저장소 루트의SKILL.md입니다. 런타임 어댑터(css/waapi/luau/porting)와spring.md는 영어만 제공되며references/에 있습니다.
모션은 시간의 언어다. 그것이 어디서 왔고, 어디로 갔고, 지금 만질 수 있는지를 말한다. 휘도는 공간의 언어다. 무엇이 위에 있고, 무엇이 살아 있고, 지금 어디를 봐야 하는지를 말한다.
따로 쓰면 각자 절반의 일만 한다. "떠오른다"는 것은 무언가를 4px 위로 옮기는 게 아니라 변위 + 더 크고 부드러운 그림자 + 더 밝은 서피스가 한꺼번에 일어나는 것이다. 그래야 비로소 뇌가 저것이 나에게 가까워졌다고 읽는다.
"딱딱하다"거나 "납작하다"고 느껴지는 인터페이스는 곡선이 우아하지 않아서 그런 경우가 거의 없다. 물리적 직관을 어겼거나, 세 가지 속성을 바꿔야 할 자리에서 하나만 바꾼 것이다.
이 팩은 어떤 API도 소유하지 않는다. 아래의 모든 판단은 사람 눈으로 검증할 수 있는 숫자로 쓰여 있다. 초 단위 지속 시간, 픽셀 단위 거리, 감쇠비, 휘도 단계. 런타임은 §2에서 한 번 고르고 나면 더 신경 쓰지 않아도 되는 세부 사항이다.
0. Language
references/의 모든 파일은 영어다. 판단을 다루는 장(이 파일, feel, contrast, recipes,
pitfalls, errata)의 전체 번역은 i18n/<locale>/에 있다.
| Locale | Path |
|---|---|
| 繁體中文 | i18n/zh-TW/ |
| 日本語 | i18n/ja/ |
| 한국어 | i18n/ko/ |
| Español | i18n/es/ |
사용자에게 그 언어들 중 하나로 답하고 있다면, 영어 파일 대신 해당 로케일의 파일을 읽어라.
어댑터(references/adapters/)는 의도적으로 영어만 제공한다. 대부분이 코드와 API 식별자이고,
거기서 번역은 잡음과 표류만 만든다.
1. 스펙 어휘
이 팩의 모든 것은 여섯 개의 용어로 쓰여 있다. 한 번만 익히면 된다. 포팅되는 것은 결국 이것들이다.
| Term | 뜻 | 표기 |
|---|---|---|
| dur | 얼마나 오래, 초 단위 | 0.25s |
| curve | out(빠르게 시작해 잦아듦), in(느리게 시작해 가속), inout, linear |
out |
| spring(Dv, b) | 시각적 지속 시간 Dv(초)와 바운스 b(0–1)로 기술한 spring |
spring(0.3, 0.15) |
| travel | 실제 원래 위치로부터의 변위, px 단위 | y −4px |
| lumin | 휘도 단계. 서피스 값 또는 그림자 tier로 표현 | surface +1 tier |
| stagger | 형제 요소 사이의 간격, 초 단위 | 0.04s |
하중을 받는 것은 spring(Dv, b)다. Dv는 잦아드는 꼬리를 제외하고 그 움직임이 보기에 걸리는 시간이며,
바로 그 덕분에 spring과 트윈을 같은 자로 나란히 놓고 비교할 수 있다. b = 0은 오버슈트 없음, b = 1은
극단적으로 튄다. 디자인 논의에서 spring을 stiffness/damping으로 지정하지 마라. 아무도 그 숫자를 머릿속에
그리지 못한다. 변환은 경계에서 한 번만 →
references/spring.md.
2. 런타임을 고르고, 그다음엔 비켜서라
지금 쓰고 있는 런타임의 어댑터를 읽어라. 하나만 읽어라. 전부 읽지 마라.
| Runtime | Adapter | Tier |
|---|---|---|
CSS만 — transition, keyframes, linear() 이징, view transitions |
references/adapters/css.md |
3 |
Web Animations API — element.animate(), ScrollTimeline |
references/adapters/waapi.md |
2 |
Luau / Roblox — TweenService, TweenInfo, RunService spring |
references/adapters/luau.md |
2–1 |
| 그 밖의 모든 것 — 게임 엔진, 네이티브 툴킷, 애니메이션 라이브러리 | references/adapters/porting.md |
— |
Tier는 런타임에서 문법이 아니라 설계를 바꾸는 유일한 항목이다.
| Tier | 런타임이 할 수 있는 것 | 설계에 미치는 결과 |
|---|---|---|
| 1 | 애니메이션을 인터럽트하고 그 인터럽트를 가로질러 속도를 이어받는다 | 이 팩의 모든 것이 쓰인 그대로 동작한다 |
| 2 | 인터럽트는 되지만 정지 상태에서 다시 시작한다(속도 소실) | 역방향 전환을 짧게 잡고, 재시작이 덜컥거림으로 보이지 않도록 b ≤ 0.15를 선호한다 |
| 3 | 끝까지 재생만 되거나, 인터럽트되면 그냥 튄다 | 사용자가 다시 트리거할 수 있는 것은 dur ≤ 0.2s로 유지하고, spring은 샘플링한 곡선으로 흉내 낸다 |
대부분의 런타임은 기본이 Tier 2이고, spring을 직접 적분해 넣었을 때만 Tier 1이 된다. 어느 쪽인지, 그리고 인터랙션에 필요하다면 어떻게 tier를 올리는지는 해당 런타임의 어댑터에 적혀 있다.
애초에 라이브러리나 spring이 필요한지부터 물어라. 요소 하나, 상태 하나, 재트리거 없음이라면 평범한 트윈으로 충분하다. spring은 인터럽트 가능성, 속도 인계, 제스처 인계를 통해 복잡도를 정당화한다. 그중 아무것도 쓰지 않는다면 비용을 낼 이유가 없다.
3. 다섯 가지 황금률
애니메이션을 쓰기 전에 다섯 개를 모두 통과시켜라. 하나라도 어기면 사람들이 대개 이름 붙이지 못하는 방식으로 결과물이 잘못됐다고 느껴진다.
1. 위치와 크기에는 spring, 불투명도와 색에는 트윈
이건 취향이 아니고 특정 라이브러리의 의견도 아니다. 뇌가 보고 있는 것을 어떻게 분류하는지에서 따라 나온다.
| 무엇이 움직이는가 | 무엇을 쓰는가 |
|---|---|
위치, 회전, 기울임 — x y rotate 및 그 친척들 |
spring(0.28, 0.2) — 약간의 오버슈트 |
| scale 계열 | spring(0.27, 0) — 오버슈트 절대 금지 |
| 불투명도, 색, 블러, 그 밖의 전부 | 트윈, dur 0.3, curve out |
| 키프레임 세 개 이상 | 트윈, dur 0.8, curve inout |
이유: 공간을 차지하는 것(위치, 크기)은 뇌가 물체로 읽고, 물체에는 질량과 관성이 있다. 불투명도와 색은 물체가 아니다. 그저 보이느냐 마느냐일 뿐이다. 그래서 거기에 spring을 쓰면 깜빡임으로 읽힌다.
따름정리: scale은 절대 바운스시키지 마라. 튀면서 커지는 것은 유리에 부딪히는 느낌으로 읽힌다.
저 네 행은 함께 캘리브레이션된 한 세트다. 무엇을 써야 할지 모르겠으면 손대지 말고 그대로 써라.
2. 애니메이션은 인터럽트 가능해야 하고, 인터럽트를 가로질러 속도를 이어받아야 한다
가장 중요한 규칙이자 가장 자주 빠뜨리는 규칙이다. 애니메이션 도중에 마음을 바꾸는 것은 정상적인 인간 행동이다.
- 가능한 곳에서는 spring을 써라. 진짜 spring 적분기는 현재 속도를 이어받으므로 역방향 전환이 급정거하지 않는다. 바로 이것이 — 통통 튀는 맛이 아니라 — spring이 트윈을 이기는 진짜 이유다.
- Tier 2/3 런타임에서는 최소한 현재 값을 읽어 거기서부터 애니메이션하라. 명목상의 시작값에서 시작하면 안 된다. 재트리거할 때마다 시작점으로 되튀는 것이 전형적인 버벅임이다.
- 하나의 단위로 취소할 수 없는 타이머로 애니메이션을 이어 붙이지 마라. 통째로 취소할 수 있는 시퀀스나 부모-자식 오케스트레이션을 써라.
- 인터럽트됐을 때 최종 상태로 튀어버리는 "전환" 메커니즘(내장 페이지 전환 API 상당수가 그렇다)은 사용자가 빠르게 재트리거할 수 있는 어떤 것에도 적합하지 않다.
3. 등장과 퇴장은 대칭이 아니다
떠나는 것을 기다리게 해서는 안 된다.
enter: opacity 0→1, y +8→0 dur 0.25 curve out
exit: opacity 1→0, y 0→+4 dur 0.15 curve in
exit는 대략 등장 지속 시간의 **0.5–0.7×**로, 더 짧은 거리를 지나간다. 떠나는 요소는 경로 전체를 갈 필요가 없다. 눈은 그것이 떠났다는 사실만 알면 된다.
4. 타이밍 tier: 역할이 다르면 예산도 다르다
| Tier | dur | 어디에 | Curve |
|---|---|---|---|
| 즉각 피드백 | 0.1–0.15s |
프레스 scale, 체크박스, 포커스 링 | out 또는 spring(0.15, 0) |
| 마이크로 인터랙션 | 0.15–0.25s |
호버, 툴팁, 버튼 색 | out |
| 컴포넌트 전환 | 0.25–0.4s |
드롭다운, 모달, 아코디언, 리플로우 | spring(0.3, 0.15) |
| 페이지 / 서사 | 0.4–0.8s |
라우트 변경, 히어로, 다중 키프레임 | spring(0.5, 0.1) + stagger |
1s 초과 |
거의 확실히 틀렸다 | 로딩, 앰비언트, 스크롤 연동만 | — |
호버 애니메이션은 0.2s를 넘겨서는 안 된다. 커서는 이미 떠났을 수 있다. 거리가 길면 조금 더 오래 가도 되지만 선형은 아니다. 거리가 두 배면 시간은 100%가 아니라 대략 20–30% 더 벌 뿐이다.
전체 논거, spring 파라미터의 인간적 의미, 오케스트레이션의 리듬, 안티패턴 →
references/feel.md
5. 하나의 사건은 여러 속성을 같은 방향으로 바꿔야 한다
속성 하나는 담는 정보가 너무 적다. 여러 속성이 함께 움직일 때 비로소 뇌가 그것을 하나의 물리적 사건으로 읽는다.
| 표현하려는 것 | 최소한 이만큼은 바꿔라 |
|---|---|
| 떠오름 / 접근 (카드 호버) | y 상승 + 그림자가 더 크고 부드럽게 + 서피스가 더 밝게 |
| 눌림 / 함몰 | scale 축소 + 그림자 수축 + 안쪽 그림자 + 어둡게 |
| 집어 든 상태 (드래그 중) | scale 확대 + 넓은 그림자 + 깊이 순서 상승 |
| 포커스 (모달 열림) | 콘텐츠 등장 + 배경 어두워짐 (스크림이 먼저 도착해야 한다) |
| 비활성 | 전용 저대비 색 토큰 (불투명도 50%가 아니다) |
휘도는 상태 신호이고 변위는 과정이다. 그래서 휘도는 움직임보다 빠르게 변한다(대략 0.12–0.15s
대 0.2–0.35s).
다크 테마에서는 그림자가 거의 보이지 않으므로, 엘리베이션은 대신 더 밝은 서피스로 표현해야 한다. 테마를 바꾸는 것은 팔레트가 아니라 메커니즘을 바꾸는 일이다.
화면에는 가끔 세 번째 것이 놓인다 — 텍스처. 해칭, 그레인, 반복되는 괘선 같은 것들이다. 이것은 채널이
아니고 스펙 용어도 아니다. 물체가 놓여 있는 바탕을 강화하는 lumin의 수식어이며, 사다리에서 가장 작은 휘도
단차보다 반드시 조용해야 한다. 그렇지 않으면 재질이 아니라 노이즈로 읽힌다. 텍스처는 텍스트에게 언제나 지기도
한다. 흐르는 텍스트 밑에는 어떤 해칭도 깔리지 않고, 글자가 있는 영역에서 텍스처가 눈에 가장 먼저 닿는 것이어서는
안 된다. 강화와 액센트에만 → contrast.md §8.
광학 모델, 라이트/다크 토큰 세트, 각 휘도 속성을 애니메이션하는 비용, 접근성 하한선 →
references/contrast.md
4. 라우팅 표
지금 눈앞에 있는 작업에 해당하는 파일을 읽어라. 전부 읽지 마라.
| 하려는 일 | 읽을 것 |
|---|---|
| "자연스럽다"가 무슨 뜻인지 이해하기, 느낌이 안 잡힐 때, 누군가 "딱딱하다"고 했을 때 | references/feel.md ← 시간 축 |
| 엘리베이션, 그림자, 다크 모드, 포커스, 스크림, 대비 | references/contrast.md ← 공간 축 |
spring(Dv, b)를 런타임이 원하는 숫자로 바꾸기 |
references/spring.md |
| 바로 가져다 쓸 완성된 효과가 필요할 때 | references/recipes.md |
| CSS / WAAPI / Luau에서 실제 호출을 작성할 때 | references/adapters/<runtime>.md |
| 여기에 내 런타임의 어댑터가 없을 때 | references/adapters/porting.md |
| 아무것도 안 움직인다, 프레임이 튄다, exit가 안 나온다, 역방향 전환이 버벅인다 | references/pitfalls.md |
| 아무것도 망가져 보이지 않고, 이제 다 됐다고 말하려는 참일 때 | references/errata.md ← 출시 전에 읽어라 |
영어가 아닌 로케일: feel, contrast, recipes, pitfalls, errata는 i18n/<locale>/로 바꿔 읽어라.
5. 접근성은 선택 사항이 아니다
큰 요소를 움직이거나 크기를 바꾸는 것은 무엇이든 reduced motion 설정을 존중해야 한다. 모든 플랫폼이 이 설정을 노출하며, 런타임별 이름은 어댑터에 적혀 있다.
올바른 동작은 "애니메이션을 끄는 것"이 아니다. 변위와 scale은 끄고, 불투명도와 색은 남기는 것이다. 사용자는 여전히 화면이 바뀌었음을 알게 되고, 슬라이드 대신 크로스페이드가 일어난다. 패럴랙스, 자동 재생 영상, 무한 루프는 언제나 그 위에 명시적인 분기가 하나 더 필요하다.
6. 성능 레드라인
보편 법칙: 컴포지팅은 싸고, 페인트는 비싸고, 레이아웃은 파멸적이다. DOM이 없는 런타임을 포함해 모든 런타임에 이 세 계층의 어떤 형태가 존재한다.
- ✅ 언제나 안전: transform(translate / scale / rotate)과 opacity — 아무것도 리플로우시키지 않는다
- ⚠️ 페인트(측정하라): 그림자, 모서리 반경, 배경색, 블러 — 작은 요소에는 괜찮고, 큰 요소나 긴 목록에서는 위험하다
- ❌ 레이아웃(피하라): width, height, top, left, margin, padding, border width — 이것들을 애니메이션하면 매 프레임 주변 전체의 레이아웃을 다시 푼다
비싼 속성의 표준 탈출구는 그 속성 자체를 애니메이션하는 대신 두 상태를 미리 렌더해 두고 불투명도를
크로스페이드하는 것이다. 이 수법은 그림자에 대해 contrast.md §6에 나오며, ⚠️ 행의 거의 모든 것으로
일반화된다.
7. 이 숫자들은 어디서 왔는가
이 팩의 파라미터 표는 캘리브레이션된 한 세트이며, 널리 쓰이는 애니메이션 구현들이 기본값으로 내놓는 숫자, 그리고 그것들이 튜닝의 근거로 삼은 지각 연구 문헌과 교차 검증되어 있다. 여기에 맨 숫자로 적어 둔 이유는 정확히, 이 팩을 쓰려고 어떤 벤더의 문서도 가져올 필요가 없게 하기 위해서이고, 라이브러리가 API를 바꿔도 여기 있는 것이 썩지 않게 하기 위해서다.
어떤 API가 무엇을 하는지에 대해 이 팩과 런타임 자체 문서가 어긋난다면 런타임 문서가 이긴다. 자기 동작을 설명하고 있는 쪽이니까. 무엇이 좋게 느껴지는지에 대해 어긋난다면 이 팩을 따르고, 그다음 네 눈으로 확인해라. 0.3s spring은 물체로 읽히거나 읽히지 않거나 둘 중 하나이고, 그건 인용이 필요한 의견 문제가 아니다.