토스쇼핑 결제 모션 / 2026.05
토스쇼핑 결제모션: WebKit Compositor z-index 불일치 대응
TL;DR
토스쇼핑 결제모션의 밀어서 결제 버튼에서 WebKit 환경에서만 그라디언트 레이어가 핸들 위로 올라오는 문제가 있었어요.
CSS상으로는 그라디언트가 z-index 1, 핸들이 z-index 100이라 핸들이 위에 있어야 했어요. 하지만 Safari/WKWebView에서는 애니메이션 시작 직후 약 0.73초 동안 의도와 다르게 그려졌어요.
z-index 숫자를 더 키우는 것보다, WebKit compositor가 두 요소를 어떤 layer로 다루는지 보는 게 먼저였어요. GSAP/Rally timeline과 Framer Motion inline transform이 함께 쓰인 구조에서 layer promotion 시점을 맞추기 위해 핸들에 will-change: transform을 제한적으로 적용했어요.
문제 정의 Problem
밀어서 결제 버튼은 그라디언트, 배경, 결제 중 오버레이, 핸들이 겹쳐 있는 구조였어요. 이 중 그라디언트는 뒤에서 흐르고 핸들은 항상 그 위에 보여야 했어요.
그런데 Safari/WKWebView 같은 WebKit 환경에서만 애니메이션 시작 직후 약 0.73초 동안 그라디언트가 핸들 앞에 보였어요. Chrome과 Firefox에서는 재현되지 않았고, 모션이 없는 정지 상태에서도 마찬가지였어요.
결제 CTA에서 직접 보이는 모션이라, 짧은 순간이라도 레이어 순서가 뒤집히면 사용자가 느끼는 완성도가 크게 떨어질 수 있었어요. 문제는 CSS의 z-index 값만 보면 이미 의도대로 설정돼 있었다는 점이에요. 그래서 단순한 stacking order 문제가 아니라, 애니메이션 중 브라우저가 layer를 승격하고 합성하는 방식까지 봐야 했어요.
접근 방식 Approach
두 레이어의 transform 갱신 방식이 서로 다르다는 점에 집중했어요. 그라디언트는 GSAP 기반 Rally timeline으로, 핸들은 Framer Motion inline transform으로 갱신되는 구조였어요.
z-index 값 자체는 의도대로였기 때문에 stacking context를 더 쌓기보다 transform animation과 compositor layer 승격 시점에 초점을 맞췄어요. WebKit에서 애니메이션 중인 그라디언트가 먼저 compositing layer로 승격되고, 초기값이 translateX(0px)인 핸들은 같은 시점에 승격되지 않은 듯했어요. 그래서 z-index가 낮은 그라디언트가 잠깐 핸들보다 위에 그려졌어요.
두 요소가 같은 compositor 경로에서 z-index 순서대로 비교되도록, 핸들에 will-change: transform을 추가했어요. transform 변경이 예정된 요소임을 브라우저에 알려줘서 layer promotion 시점을 맞추는 방향이었어요.
핵심 결정 Decision
z-index를 조정하기보다 layer promotion을 맞추는 방식으로 풀었어요.
wrapper에는 이미 isolation: isolate가 적용돼 있었고 z-index 값도 의도대로였어요. 그래서 stacking context를 더 손대는 건 실제 원인과 거리가 멀다고 봤어요.
다만 will-change는 GPU 메모리를 쓸 수 있어서 넓게 적용하지 않았어요. 문제가 생긴 핸들에만 제한적으로 적용해서, WebKit 환경에서 layer 승격 조건을 맞추는 데 필요한 범위로만 사용했어요.
해결 결과 Result
WebKit 환경에서도 밀어서 결제 버튼의 그라디언트가 핸들 뒤로 자연스럽게 지나가도록 고쳤어요.
레이어 구조를 크게 바꾸지 않고, 문제가 생긴 요소의 layer promotion만 조정해 최소 변경으로 해결했어요. CSS z-index 값 자체를 더 키우는 방식이 아니라 실제로 어긋난 compositor 조건을 맞춘 수정이었어요.
배운 점 Learning
겉으로 드러난 z-index 수치가 정상이어도, transform을 다른 방식으로 갱신하는 요소가 겹치면 원인은 stacking context가 아니라 compositing layer 쪽일 수 있다는 걸 배웠어요.
다음에 비슷한 증상을 보면 CSS z-index부터 의심하기보다, 브라우저가 각 요소를 어떤 compositing layer로 다루고 언제 승격하는지를 먼저 확인하려고 해요. GSAP/Rally timeline과 Framer Motion처럼 갱신 경로가 다른 요소가 한 화면에 겹칠 때는 특히요.