토스쇼핑 결제 모션 / 2026.05
토스쇼핑 결제모션: 구현 방식 전환과 성능 개선
TL;DR
토스쇼핑 결제 플로우의 밀어서 결제와 바텀시트 전환 모션을 구현하고, 출시 후 운영 피드백에 따른 롤백과 재구현까지 맡았어요. 이 글은 그 과정에서 저사양 환경의 클릭 직후 멈춤을 줄이기 위해 모션 구조와 렌더링 우선순위를 바꾼 작업을 다뤄요.
Rally로 모션을 완성한 뒤 저사양 기기에서도 더 부드럽게 동작하도록 개선해달라는 요청을 받았어요. 바로 버튼을 누른 직후를 프로파일링해보니 모션에도 적지 않은 비용이 있었지만, 가장 긴 멈춤은 비즈니스 로직과 바텀시트 렌더링이 한꺼번에 실행될 때 생겼어요.
Rally 타임라인을 Framer Motion의 상태 중심 구조로 옮기고 바텀시트 상태 업데이트에 startTransition을 적용해, 클릭 모션과 뒤따르는 렌더링의 우선순위를 나눴어요. 6x CPU throttle 조건에서 전후 각각 30회 측정했을 때, 클릭 직후 500ms 내 최대 frame gap이 300ms를 넘은 실행은 기존 30회에서 최종 구현 0회로 줄었어요.
문제 정의 Problem
Rally 타임라인은 모션 관련 코드를 한곳에 모아 순서를 제어하기 좋은 구조였어요. 하지만 저사양 기기 개선 요청을 받고 클릭 직후를 확인해보니, 가장 긴 멈춤은 모션만의 문제가 아니었어요.
버튼을 누르면 비즈니스 로직과 바텀시트 렌더링이 모션과 같은 구간에 몰렸어요. 연산량을 줄이려면 비즈니스 로직을 바꾸거나 모션을 단순화해야 했지만, 둘 다 선택할 수 없었어요.
전체 작업을 줄이기 어려운 조건에서, 클릭 직후 한 시점에 몰리는 렌더링의 우선순위를 나눠 가장 긴 멈춤을 줄일 방법이 필요했어요.
접근 방식 Approach
먼저 문제를 클릭 직후 500ms 안에서 일어나는 frame gap으로 좁혔어요. 결제 CTA를 누른 뒤 어느 시점에서 프레임 간격이 크게 벌어지는지 확인하고, 그 구간의 모션과 비즈니스 로직, 바텀시트 렌더링을 함께 살펴봤어요.
바텀시트 상태 업데이트를 startTransition으로 처리해, 즉시 보여야 하는 클릭 모션과 뒤따르는 렌더링의 우선순위를 나누려 했어요. 하지만 Rally 타임라인에서는 모션이 정상적으로 재생되지 않았어요. 이 제약을 확인한 뒤, 같은 모션을 유지하면서 상태 업데이트를 분리해 다룰 수 있도록 Framer Motion의 상태 중심 구조로 옮겼어요.
수정 뒤에는 Playwright와 CDP를 사용해 같은 6x CPU throttle 조건에서 전후 각각 30회씩 측정했어요. warmup 2회는 제외하고, 클릭 직후 500ms 내 최대 frame gap의 p50·p95와 300ms 초과 횟수를 비교했어요.
핵심 결정 Decision
클릭 직후 병목을 풀기 위해 바텀시트 상태 업데이트에 startTransition을 적용하려 했어요. 그러나 Rally 타임라인에서는 startTransition을 적용하면 모션이 정상적으로 재생되지 않았어요.
각 모션을 분리하면 이 문제는 피할 수 있었지만, 관련 코드를 한곳에서 관리하려고 Rally를 쓴 이유도 사라졌어요. 그래서 Rally 타임라인을 Framer Motion의 상태 중심 구조로 옮기고, 모션과 바텀시트 상태 업데이트를 분리해 startTransition을 적용했어요.
타임라인에 모아두던 모션을 여러 상태로 나눠 관리해야 해 코드는 더 복잡해졌어요. 그 비용을 감수하고도, 클릭 모션과 뒤따르는 렌더링의 우선순위를 구분할 수 있는 구조를 선택했어요.
해결 결과 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 내 최대 frame gap p50은 357.5ms에서 241.7ms로 32.4% 줄었어요. p95도 400.5ms에서 283.4ms로 줄었어요.
기존 구현에서는 30회 모두 300ms를 넘었지만, 최종 구현에서는 한 번도 넘지 않았어요. 이 수치는 Framer Motion 전환과 startTransition을 함께 적용한 최종 구현의 결과이며, 두 변경 중 하나의 효과로 따로 나누어 보지는 않았어요.
측정에서 확인한 개선은 클릭 직후 가장 긴 프레임 간격이 줄었다는 점이에요. 클릭 후 500ms 내 long task 총량의 p50은 오히려 늘었기 때문에, 전체 작업량이 줄었다고 해석하지는 않았어요. 결과는 같은 저사양 조건에서 클릭 직후의 긴 멈춤이 완화됐다는 범위로 한정했어요.
배운 점 Learning
모션이 느리게 보인다고 해서 모션 연산만 줄여야 하는 것은 아니었어요. 실제로 가장 오래 멈추는 구간을 먼저 찾고, 바꿀 수 없는 작업과 바꿀 수 있는 실행 순서를 나눠 본 것이 이번 결정의 출발점이었어요.