motion은(는) 무엇을 하나요?
이 기술은 모션을 구축합니다. product-ui/references/values.md의 모션 섹션을 확장하며, 해당 파일에는 확정된 숫자(이징 커브, 누르기 스케일, 바운스, 스태거, 지속 시간 표, 토스트 및 레이아웃 지속 시간, 이동 거리, 종료 대 진입 비율)가 포함되어 있습니다. 이러한 숫자는 여기에 반복되지 않습니다. 이 기술은 결정 순서, 토큰, 레시피 및 실패 모드를 추가합니다.
결정을 내리기 전에 product-ui §1(취향 저널 및 우선 순위)과 §5(각 기술이 소유하는 것)를 적용합니다.
결정 순서
이 지침을 위에서 아래로 따르십시오. 각 단계에서 작업을 종료할 수 있습니다. 애니메이션 라인이 0개여도 유효한 결과이며, 그렇게 명시합니다.
1. 움직여야 하는가? 빈도 게이트
한 사용자가 하루에 이 요소를 몇 번 보는지 묻습니다. 등급은 values.md에 있습니다. 실제로:
- 키보드에 의해 시작되거나 하루에 100회 이상 보이는 경우: 움직임 없음. 이유: 반복적인 움직임은 지연으로 바뀝니다.
- 하루에 수십 회 보이는 경우(호버, 탭 전환, 행 선택): 없음 또는 즉각적인 색상 변경.
- 사용자가 읽거나 작업 중인 데이터(테이블의 숫자, 라이브 대시보드의 차트)는 스타일을 위해 절대 움직이지 않습니다. 이유: 움직임은 읽고 있는 것에서 시선을 돌립니다.
- 게이트가 '아니오'라고 말하면 애니메이션을 작성하지 마십시오. 어떤 등급에 해당하는지 말하고 정적 대안(즉각적인 상태 변경)을 제시하십시오.
2. 목적 이름 지정
한 단어를 고르십시오: 공간적(어디에서 왔거나 갔는지), 상태(이것이 변경됨), 피드백(들었습니다), 연속성(점프를 방지함), 설명(드문 순간에만). 이름을 지정할 수 없으면 중단하십시오. 이유: 빈번한 요소에 대해 "멋져 보인다"는 느린 UI의 주요 원인입니다.
3. 도구 선택: 작동하는 가장 저렴한 것
| 필요 | 도구 |
|---|---|
| 클래스, 속성, 호버 또는 누름으로 전환되는 상태 | CSS 전환 |
display: none, 팝오버 또는 대화상자에서 진입 | @starting-style + transition-behavior: allow-discrete |
| 일회성 시퀀스, 스태거 또는 페이지가 바쁜 동안 실행되어야 하는 모션 | CSS @keyframes |
JS가 시점을 결정, 취소, 역방향, finished 또는 FLIP 필요 | WAAPI el.animate() |
| 스프링, 속도 핸드오프, 마운트 해제되는 React 노드의 종료, 공유 레이아웃 | Motion (motion/react) |
요소가 전체 컴포넌트(메뉴, 토스트, 드로어)인 경우 포커스 처리를 제공하는 라이브러리를 사용하고 pick-library를 요청하십시오. 페이드를 위해 Motion을 설치하지 마십시오. 이유: CSS는 메인 스레드에서 실행되지 않으며 번들을 소모하지 않습니다.
4. 속성
transform및opacity를 애니메이션 처리하고,filter는 ≤ 2px 스왑 블러에만 사용합니다. 모든 속성의 이름을 지정하고 절대transition: all을 사용하지 마십시오.- 높이 및 레이아웃 변경:
references/recipes.md(그리드 행, FLIP)를 참조하십시오. 무언가를 움직이기 위해width,top,left또는margin을 절대 애니메이션 처리하지 마십시오. 이유: 각각은 모든 프레임에서 레이아웃을 강제합니다. - 절대
box-shadow를 애니메이션 처리하지 마십시오. 대신 의사 요소의opacity를 페이드하십시오. translate백분율은 요소 자체 크기에 상대적이므로translateY(100%)는 모든 높이의 드로어와 토스트에 적합합니다.
5. 커브 및 지속 시간, 사용법에 따라 일치
추측에 가장 가까운 숫자를 선택하지 마십시오. 사용법("메뉴 닫기", "아이콘 교체")을 명시한 다음 references/tokens.md에서 해당 행을 가져오십시오. 사용법과 일치하는 행이 없으면 사용법의 이름을 딴 새 토큰을 추가하고 기존 토큰을 구부리지 마십시오. 이유: 토큰은 모션이 무엇을 위한 것인지에 대한 진술이므로 잘못된 사용법이 있는 비슷한 숫자는 잘못된 토큰입니다.
- 진입 또는 종료:
--ease-out. 화면에서 이동:--ease-in-out. 드로어:--ease-drawer. 색상:ease. 스피너:linear. 절대ease-in을 사용하지 마십시오. - 드래그 가능하거나 비행 중 인터럽트 가능한 모든 것은 커브가 아닌 스프링을 사용합니다. 이유: 커브는 사용자의 손이 가진 속도를 받아들일 수 없습니다.
- 느낌이 느린 경우: 먼저 지속 시간을 줄인 다음 거리를 줄이고, 마지막으로 커브를 수정합니다.
6. 인터럽션
- 사용자가 1초에 두 번 트리거할 수 있는 모든 것은 전환 또는 스프링이어야 하며 절대
@keyframes가 아닙니다. 이유: 전환은 현재 값에서 대상을 다시 지정하지만 키프레임은 다시 시작하여 점프합니다. - WAAPI: 현재 값을 읽은 후 이전 애니메이션을 취소하고 그 값에서 새 애니메이션을 시작합니다(
references/recipes.md). - 10번 스팸으로 토글된 것은 점프 없이 올바른 상태로 끝나야 합니다. 테스트하십시오.
7. 종료
- 기본 종료는 페이드이며 진입보다 짧습니다(토큰 파일에 각 쌍이 있습니다).
- 방향이 있는 것(드로어, 시트, 스와이프된 토스트)은 왔던 길로 떠납니다.
- 종료되는 요소는 즉시 공간을 차지하지 않으므로 형제 요소가 페이드되는 동안 간격을 닫습니다.
- 종료는 동시에 이루어지며 절대 스태거되지 않습니다. 이유: 사용자는 이미 떠나기로 결정했습니다.
8. 감소된 모션, 애니메이션과 함께 제공
불투명도와 색상은 유지됩니다. 슬라이드, 스케일, 블러 및 바운스는 사라지고 슬라이드는 페이드가 되고 바운스는 0이 됩니다. 모션을 유일한 신호로 사용하지 마십시오. 패턴은 references/recipes.md에 있습니다.
안무
타이밍을 정하기 전에 모든 움직이는 요소에 역할을 지정하십시오:
| 역할 | 처리 |
|---|---|
| 리드(주목해야 할 하나) | 먼저 움직이고, 가장 많이 이동 |
| 서포트 | 따라가고, 덜 이동 |
| 장식 | 제자리에서 페이드 또는 아무것도 하지 않음 |
| 정적(컨테이너, 크롬) | 절대 움직이지 않음 |
- 컨테이너당 하나의 진입. 패널이 슬라이드 인하면 콘텐츠는 이미 거기에 있습니다. 이유: 두 개의 계층적 진입은 사용자가 두 번 기다리게 만듭니다.
- DOM 순서가 아닌 중요도 순서로 정렬합니다. 지연은 values.md 단계(항목 간 40ms, 제목, 텍스트 및 작업 간 100ms)를 유지합니다. 지연보다는 역할에 따라 거리와 지속 시간을 변경합니다.
- 목록 스태거를 인덱스 6(마지막 지연 240ms)으로 제한하고 그 이후의 항목은 그 지연을 공유합니다. 재생 중에 입력이 작동합니다.
- 메뉴, 명령 팔레트, 탭 콘텐츠 또는 열린 반복을 절대 스태거하지 마십시오.
- 가만히 있음: 사용자가 클릭하려는 것, 페이지가 다시 로드될 때 이미 화면에 있는 것, 마지막 업데이트 후 150ms 이내에 도착하는 업데이트(종료 상태로 점프).
- 방향은 의미를 따릅니다: 다음은 오른쪽에서 진입하고 뒤로는 왼쪽에서 진입하며 사용자가 트리거한 것은 트리거에서 바깥쪽으로 커집니다.
필요할 때 로드
- references/tokens.md: 정확한 지속 시간, 거리, 스케일, 블러, 이징 또는 스프링 값과 기존 코드베이스를 토큰화할 때 로드합니다.
- references/recipes.md: 메뉴, 팝오버, 대화상자, 드로어, 토스트, 목록 진입, 아이콘 교체, 높이 변경, 탭 표시기 또는 숫자 변경을 구축할 때 로드합니다.
- references/gestures.md: 드래그, 스와이프, 플릭, 시트 또는 포인터를 따르는 모든 것에 대해 로드합니다.
- references/performance.md: 모션이 프레임을 드롭할 때,
will-change를 사용하기 전, 그리고 업데이트가 빠르게 도착할 때 로드합니다.
쌍
| 깨짐 | 수정됨 | 이유 |
|---|---|---|
.menu { transition: all 300ms ease-in } | transition: opacity 200ms, transform 200ms var(--ease-out) | 명명된 속성, ease-in은 느림, 메뉴 ≤ 200ms |
scale(0)에서 scale(1) | scale(0.95) 및 opacity: 0에서 1 | 무에서 나타나는 것은 없음 |
| 팝오버가 중심에서 스케일 | transform-origin: var(--transform-origin) | 트리거에서 바깥쪽으로 커져야 함 |
.open { animation: slide-in 300ms } | 토글된 상태의 transition: transform | 키프레임은 인터럽트될 때 스냅됨 |
모든 행 animation-delay: i * 120ms | min(i, 6) * 40ms | 마지막 행이 300ms 이내에 도착 |
| 드로어가 슬라이드 인 후 페이드 아웃 | 왔던 길로 다시 슬라이드 아웃 | 방향이 설명임 |
고정된 ease-out에서 드래그 릴리스 | 릴리스 속도로 시드된 스프링 | 손의 운동량이 계속됨 |
pointermove에서 setY(y) | el.style.transform = ... | 프레임당 리렌더링 없음 |
마치기 전에
diff를 검색한 다음 확인하십시오.
| 검색 대상 | 수정 |
|---|---|
transition: all, *의 will-change, ease-in | 명명된 속성, 제거, --ease-out |
scale(0), 팝오버의 transform-origin: center | 0.95, 트리거 원점(대화상자는 중앙 유지) |
토글되는 클래스의 @keyframes | 전환 |
| tokens.md의 행보다 긴 지속 시간 또는 tokens.md에 없는 숫자 | 사용법의 토큰 |
keydown 핸들러 또는 팔레트의 모션 | 제거 |
전환의 width, height, top, left, box-shadow | transform, opacity 또는 레시피 |
prefers-reduced-motion 경로 없음 | 추가 |
실행 중인 앱에서 확인: 4배속으로 재생(DevTools 애니메이션 패널 또는 지속 시간 곱하기), 토글 스팸, 감소된 모션을 켠 상태에서 테스트, CPU 4배 스로틀. 실행할 수 없었던 것을 말하십시오.