css · high priority
CSS 애니메이션 — *transition · animation · 60fps* 의 원리
*렌더링 파이프라인* 위에서 *Composite-only* 속성으로 부드러움을 만든다
학습 개요
탄생 배경
쉬운 설명
복잡한 개념을 실생활 비유로 설명합니다.
“*무대 조명 vs 소품 옮기기*”
60fps 애니메이션은 *공연장의 무대 운영* 과 같습니다. *opacity* 와 *transform* 은 *조명* 입니다 — 조명 색을 바꾸거나 비추는 각도를 돌리는 일은 *무대 자체를 다시 짓지 않고도* 즉시 가능합니다 (컴포지터 스레드). 반면 *width/top/left* 같은 속성은 *소품을 옮기는 일* 입니다 — 한 소품을 옆으로 밀면 옆에 놓인 소품도 같이 자리를 *재배치* 해야 하고 (Layout), 페인트도 다시 칠해야 합니다 (Paint). 공연 한 막 (≈ 16.7ms) 안에 모든 소품 재배치가 끝나야 *프레임 드롭이 없는 부드러움* 이 됩니다. *will-change* 는 *"이 소품 곧 움직일 거야 — 옆에 미리 두고 준비해 둬"* 라는 *메모* 와 같지만, 모든 소품에 메모를 붙이면 *백스테이지가 메모로 가득 차* 오히려 운영이 느려집니다.
핵심 개념
| 속성 예시 | 트리거 단계 | 비용 | 메모 |
|---|---|---|---|
width height top left margin padding | Layout + Paint + Composite | 🔴 매우 높음 | *reflow* 발생 — 형제·자손까지 영향 |
color background box-shadow border-radius | Paint + Composite | 🟡 중간 | 레이아웃은 그대로, 픽셀만 다시 |
transform opacity filter (compositor 가능 시) | Composite only | 🟢 낮음 | *컴포지터 스레드* 에서 처리 — 메인 스레드 독립 |
*"transform/opacity 만 애니메이션하라"* 의 진짜 의미
두 속성은 *합성 단계만* 다시 돌리고 *컴포지터 스레드* 에서 GPU 가 처리할 수 있다. 메인 스레드가 *무거운 작업* 으로 막혀도 *애니메이션은 살아남는다*. 반대로 top 으로 같은 위치 변화를 만들면 매 프레임 *전체 레이아웃* 이 다시 돌아 메인 스레드 부하 + 형제 요소까지 흔들 수 있다.
같은 시각 효과, *왼쪽으로 이동*
- *Layout* 부터 다시 — 부모/형제까지 영향
- 메인 스레드 사용 → 다른 작업과 경합
- 60fps 깨지기 쉬움
1.box {2 position: absolute;3 transition: left 300ms ease;4}5.box.move { left: 200px; }
- *Composite* 만 — 레이아웃 영향 없음
- 컴포지터 스레드 → 메인 스레드와 독립
- GPU 텍스처로 처리 → 60fps 유지
1.box {2 transition: transform 300ms ease;3}4.box.move { transform: translateX(200px); }
실무 적용
어떤 상황에서 사용하는가
커머스 상세 페이지 — *상품 카드 호버 시 살짝 떠오름*, *장바구니 담기 클릭 후 카운트 배지 펄스*, *모달 등장 (배경 페이드 + 본문 슬라이드)*, *이미지 그리드 → 상세 뷰로 매끄러운 전환 (FLIP/View Transitions)*, *접근성 (Reduce Motion) 준수*.
어떻게 적용하는가
(1) *호버* 는 `transform: translateY(-4px)` 와 `box-shadow` 의 *transition* 으로 — `top` 사용 금지. (2) *펄스 배지* 는 `@keyframes` + `transform: scale` 무한 반복, 단 *3 회 후 멈추는 것이 UX 에 더 좋음* (시선 분산 방지). (3) *모달* 은 `opacity` + `transform: translateY(20px) → 0` 의 *동시 transition* 으로 *합성 트랙* 만 사용. (4) *그리드 → 상세* 전환은 *View Transitions API* (`document.startViewTransition(() => mutateRoute())`) 를 우선 시도, 미지원 브라우저는 *수동 FLIP* 폴백. (5) `@media (prefers-reduced-motion: reduce)` 분기로 *transition 제거* 또는 *opacity 만* 남기기. (6) `will-change: transform` 은 *호버/모달 시작 직전* 에만 켜고 `transitionend` 에서 *해제* — `useEffect` cleanup 에서도 보장. (7) *Performance 탭* 으로 정기 측정, *frame drop* 보이면 *paint flashing* 으로 페인트 영역 확인.
흔한 실수와 안티패턴
- `width`/`height`/`top`/`left` 로 애니메이션 → *Layout 매 프레임* — `transform` 으로 대체.
- `transition: all` 사용 → *예상 못한 속성까지* 보간되어 *페이지 첫 페인트* 가 흔들림. 명시적 속성만.
- `will-change` 를 *영구 적용* → GPU 메모리 폭증. 시작 시 켜고 끝나면 해제.
- `prefers-reduced-motion` 미고려 → 접근성 위반 + 어지럼증 호소.
- `transform: scale` 안에 *텍스트* → 폰트 *재페인트* 비용. 보통 무시 가능하지만 *서브픽셀 흐림* 이 보일 수 있음.
- JS `setInterval` 로 매 16ms 스타일 변경 → *메인 스레드 점거*. `requestAnimationFrame` 또는 CSS 로.
흔한 오해
*"transform 은 무조건 GPU 에서 처리된다"*
교정*조건부* 다. 브라우저가 *애니메이션 중* 인 transform 을 발견하면 자동으로 레이어를 분리한다. 단순히 정적인 `transform: rotate(45deg)` 는 *합성 단계는 가지만 별도 레이어가 아닐 수 있다*. *애니메이션 트리거* 가 있어야 GPU 텍스처로 분리된다.
왜 중요레이어 승격은 *비용이 있는 결정* 이라 브라우저가 필요한 시점에만 한다.
*"`transform: translateZ(0)` 해킹은 여전히 표준이다"*
교정*레거시 트릭* 이다. 과거에는 강제 레이어 분리 트릭으로 썼지만, *명시적 의도 표현* 이 가능한 `will-change: transform` 이 W3C 표준이다. 새 코드에는 `will-change` 또는 *애니메이션을 자연스럽게 트리거* 하는 방식 사용.
왜 중요translateZ(0) 은 *우연히* 동작하는 부수효과일 뿐, 명세에 정의된 동작이 아니다.
*"animation-duration 은 짧을수록 좋다"*
교정*인지 한계* 가 있다. 너무 짧으면 (≤ 80ms) *깜박임* 으로 인식되어 변화 자체를 놓치고, 너무 길면 (≥ 600ms) *지루함* 을 준다. 마이크로 인터랙션은 *150~250ms*, 대형 전환은 *300~500ms* 가 통상.
왜 중요인간 지각의 *연속 변화 최소 시간* 과 *주의 지속* 사이의 균형.
면접 질문
답변 방향 힌트
"Layout/Paint/Composite", "컴포지터 스레드", "GPU 메모리".
반드시 언급할 키워드
- `top` → *Layout 부터* — 부모/형제까지 reflow
- `transform` → *Composite only* — 별도 레이어 + 컴포지터 스레드
- 컴포지터 스레드는 *메인 스레드와 독립* → JS 가 무거워도 60fps
- `will-change` 는 *힌트* — 브라우저가 미리 레이어 준비
- *항상 켜두면 GPU 메모리 폭증* — 직전 켜고 직후 끄기
- `transitionend` / `animationend` 에서 `will-change: auto` 로 해제
예상 꼬리 질문
- `opacity` 도 *Composite-only* 인 이유를 픽셀 합성 모델로 설명해 주세요.
- iOS Safari 에서 `position: fixed` 안의 `transform` 이 *떨림 (jitter)* 을 보이는 원인은?
자기 점검
`Layout → Paint → Composite` 중 `transform` 이 트리거하는 단계는?
기대 키워드
자주 하는 오해
*"transform 은 Layout 부터 다시 돈다"* — 합성 단계만 다시 돌고 *컴포지터 스레드* 가 처리.
`will-change: transform` 을 *영구 적용* 하면 생기는 부작용은?
기대 키워드
자주 하는 오해
*"많이 쓸수록 빠르다"* — 별도 텍스처를 모두 메모리에 올려 오히려 느려진다.
FLIP 의 4 단계를 한 단어씩 답하라.
기대 키워드
자주 하는 오해
*"순서가 중요하지 않다"* — F → L → I → P 순서가 핵심.
*전정 장애* 사용자를 위한 표준 CSS 분기는?
기대 키워드
자주 하는 오해
*"줄이는 것만으론 부족하다"* — 큰 방향성 모션은 *제거 또는 opacity 대체* 가 표준.
학습 자료
- MDN — CSS Animations`@keyframes`, `animation-*` 속성, 단축 표기 정본.DocMDN
- MDN — CSS Transitionstransition 속성과 trigger / fill 모델.DocMDN
- web.dev — Animations performance*어떤 속성이 어느 단계를 트리거하는가* 의 표준 가이드.Docweb.dev
- MDN — will-change`will-change` 의 의미·주의·해제 패턴.DocMDN
- MDN — View Transitions APIFLIP 의 표준화 후속. SPA/MPA 전환 통합.DocMDN
- FLIP Your AnimationsFLIP 패턴의 *오리지널 정리*. 4 단계 구조의 역사.BlogPaul Lewis (Aerotwist)
- MDN — prefers-reduced-motion접근성 모션 감소 미디어 쿼리.DocMDN