토스쇼핑 결제 모션 / 2026.05
토스쇼핑 결제모션: 구현 방식 전환과 성능 개선
TL;DR
토스쇼핑 결제 플로우의 밀어서 결제 모션은 처음에 GSAP 기반 Rally timeline으로 구현했어요. 모션과 책임은 분리해뒀지만 스테이징 과정에서 저사양 기기 프레임드랍이 발견됐고, Framer Motion 기반의 state 중심 구현으로 재구성하면서 startTransition으로 병목을 분산했어요.
문제 정의 Problem
최초 구현은 Rally timeline 기반이었고 구조 자체가 무질서하진 않았어요. 기존에도 바텀시트는 마운트되고 있었는데, 여기에 결제 전환 모션이 더해지면서 저사양 기기에서 렌더링 병목이 드러났어요. 이 이슈는 PR이 머지된 뒤 스테이징에서 다른 버그로 잠시 홀드되던 중에 다른 개발자분이 발견해 제보해주셨어요. 사용자 결제 흐름에서 바로 보이는 전환 모션이라, 단순히 부드러워 보이는지보다 저사양 조건에서도 체감 멈춤을 줄이는 게 중요했어요.
접근 방식 Approach
결제 전환 모션을 React state 흐름에 맞춰 재구성했어요. 모션과 책임은 분리돼 있던 기존 GSAP/Rally timeline 구현을 Framer Motion 기반 state 중심 구조로 옮기고, 브라우저 animation engine이 처리하기 좋은 transform 속성은 그대로 살렸어요. 결제 전환 중 한꺼번에 몰리던 렌더링 작업은 startTransition으로 분산했어요.
핵심 결정 Decision
GSAP 기반 Rally timeline은 복잡한 모션을 명시적인 timeline으로 제어하기 좋았어요. 다만 이 결제 플로우에서는 state 변화와 렌더링 병목을 함께 다뤄야 했는데, timeline 중심 구현만으로는 저사양 기기의 체감 멈춤을 줄이기 어려웠어요. Framer Motion으로 옮긴 건 React state 흐름과 모션 상태를 자연스럽게 맞추기 위해서였어요.
병목은 라이브러리만의 문제가 아니었어요. WAAPI의 영향은 생각보다 미미했고, 정작 유의미했던 개선은 바텀시트 전환 중 발생하던 렌더링 작업을 startTransition으로 분산한 데서 나왔어요. 프레임 저하 원인을 animation engine만의 문제로 보지 않고 React 렌더링 병목까지 같이 다뤄야 했어요.
해결 결과 Result
- 클릭 후 500ms 내 최대 frame gap p50
- 357.5ms → 241.7ms
- 클릭 후 500ms 내 최대 frame gap p95
- 400.5ms → 283.4ms
- 300ms 이상 멈춤 발생률
- 30/30 → 0/30runs
6x CPU throttle 환경에서 클릭 직후 500ms 내 peak frame freeze p50을 357.5ms에서 241.7ms로 줄였어요. 약 32.4% 감소였어요. p95도 400.5ms에서 283.4ms로 약 29.2% 줄었어요. 기존 구현에서는 30회 중 30회 모두 300ms 이상 멈췄지만, 재구현 이후에는 30회 중 0회로 줄었어요. 저사양 조건에서 구매 CTA 클릭 직후 전환 초기의 체감 멈춤이 크게 줄었어요.
배운 점 Learning
애니메이션 성능 문제는 라이브러리를 바꾸는 것만으로 해결된다고 보기 어렵다는 걸 배웠어요. 진짜 개선은 실제 병목이 어디 있는지 찾아 그 blocking을 분산한 데서 나왔어요.