🤖
1727 in / 1618 out / 3345 total tokens
모바일 검색 오버레이에서 하단 검색 CTA 버튼이 의도보다 훨씬 큰 히트박스를 갖고 있었다. 화면 상단의 뒤로가기, 테마 토글 버튼이 터치되지 않아 원인을 추적했다.
범인은 animate-slide-up와 gpu-accelerate 클래스 조합이었다. Intercom의 keyboard-aware 래퍼와 결합되면서 CSS transform 기반 슬라이드 애니메이션이 레이아웃 계산에 간섭했다. 결과적으로 footer 요소가 뷰포트 전체 높이까지 확장되어 상단 버튼들의 터치 이벤트를 가로챘다. 게임 개발에서 hitbox 디버깅할 때와 똑같은 느낌이었다. 콜리전 메시가 시각 메시보다 크면 플레이어는 보이지 않는 벽에 부딪힌다. 여기서도 마찬가지로 보이지 않는 터치 영역이 UI를 막고 있었다.
해결은 단순하게 해당 두 클래스를 제거하는 것이다.
tsx // Before className="fixed inset-0 z-[2147483000] flex flex-col overflow-hidden animate-slide-up gpu-accelerate"
// After className="fixed inset-0 z-[2147483000] flex flex-col overflow-hidden"
gpu-accelerate는 will-change: transform을 추가해 GPU 레이어를 강제 생성한다. animate-slide-up은 translateY 기반 진입 애니메이션이다. 둘 다 transform을 건드리니 keyboard-aware의 높이 계산 로직과 충돌한 것으로 보인다. 슬라이드 애니메이션을 포기해야 해서 아쉽지만, 터치가 안 되는 것보다 낫다.
테스트도 보강했다. 기존에는 footer가 뷰포트 하단에 닿아있는지만 검증했다. 이번에 상한 검증을 추가했다.
ts expect(footerBox!.y + footerBox!.height).toBeLessThanOrEqual(viewportSize!.height + 2);
footer의 하단 끝이 뷰포트 높이를 초과하지 않는지 확인한다. ±2px 톨러런스는 서브픽셀 렌더링 오차를 감안한 것이다. UE5에서 콜리전 테스트할 때도 비슷한 epsilon을 쓴다. 정밀도 문제는 플랫폼을 가리지 않는다.
진행 로그에는 sticky footer에서 footer로 표현을 수정했다. position: sticky가 아니라 fixed + justify-end 조합이므로 용어가 오해를 줄 수 있었다.
교훈은 transform 기반 애니메이션과 동적 레이아웃 계산을 섞을 때 주의하라는 것. GPU 가속이 만능이 아니다. 레이아웃에 영향을 주는 요소와 조합하면 hitbox가 튀어나간다.
애니메이션은 눈을 즐겁게 하지만, hitbox까지 팽창시키면 사용자는 보이지 않는 벽에 부딪힌다.