모바일 최적화 — 생각보다 까다로운 모바일 UX
데스크톱에서 완벽했던 앱이 모바일에서 무너지다
"데스크톱에서 잘 되는데?" 이 말은 프론트엔드 개발에서 가장 위험한 문장 중 하나다. 타로 마스터를 처음으로 내 스마트폰에서 열었을 때, 화면 하단이 잘려 있었다. 카드 뒤집기 애니메이션이 버벅거렸다. 텍스트가 너무 작아서 읽을 수 없었다. 데스크톱에서의 그 우아한 경험은 어디로 간 걸까.
웹 트래픽의 절반 이상이 모바일에서 발생한다. 타로 마스터 같은 가벼운 엔터테인먼트 앱은 그 비율이 더 높을 수밖에 없다. 출퇴근길에, 침대에서, 친구와 커피를 마시다가 "타로 한번 볼까?"라고 꺼내는 게 일상적 사용 시나리오다. 모바일 경험이 나쁘면 사용자의 대부분을 잃는다는 뜻이다.
하단 탭 바: 네이티브 앱의 감각 빌리기
모바일 앱에서 가장 자연스러운 네비게이션은 하단 탭 바다. 인스타그램, 카카오톡, 거의 모든 네이티브 앱이 하단 탭을 사용한다. 엄지손가락이 자연스럽게 닿는 위치에 주요 메뉴를 배치하는 것이 모바일 UX의 기본 중의 기본이다.
타로 마스터에도 하단 탭 바를 도입했다. 홈, 리딩, 히스토리, 설정. 네 개의 탭으로 구성했다. 웹앱에서 하단 탭 바를 구현하는 건 기술적으로 어렵지 않다. CSS의 position: fixed와 bottom: 0을 쓰면 된다. 하지만 여기서부터 진짜 문제가 시작된다.
모바일 브라우저에는 자체 UI가 있다. 주소창, 하단 도구 모음. 이것들이 화면 공간을 차지하면서 우리가 만든 하단 탭 바와 충돌한다. 특히 스크롤할 때 브라우저 UI가 나타났다 사라졌다 하면서 레이아웃이 춤을 추는 현상이 발생한다.
CSS의 환경 변수 env(safe-area-inset-bottom)을 사용해서 이 문제를 완화했다. 이 값은 노치나 홈 인디케이터 같은 시스템 UI 영역의 크기를 알려준다. 하단 탭 바의 패딩에 이 값을 더하면 시스템 UI와 겹치지 않는 안전한 영역에 탭이 위치한다. 하지만 이건 시작에 불과했다.
Chrome 모바일의 악명 높은 하단 탭 버그
가장 고통스러웠던 건 Chrome 모바일 브라우저의 하단 탭 위치 버그였다. Chrome은 스크롤 방향에 따라 하단 URL 바를 보여주거나 숨기는데, 이 과정에서 viewport 높이가 동적으로 변한다. 문제는 이 높이 변화가 CSS의 100vh 값에 반영되지 않는 경우가 있다는 것이다.
결과적으로 우리의 하단 탭 바가 Chrome의 하단 바 뒤에 숨어버리거나, 반대로 Chrome의 바가 사라졌을 때 하단에 어색한 빈 공간이 생기는 현상이 반복됐다. "왜 이러지?"를 반복하며 수십 번 스크롤을 올렸다 내렸다 했던 기억이 생생하다.
해결 과정은 이랬다. 먼저 CSS의 100vh 대신 100dvh(dynamic viewport height)를 시도했다. 이 단위는 브라우저 UI의 동적 변화를 반영하는 최신 CSS 단위다. 대부분의 상황에서 개선됐지만, 특정 Android Chrome 버전에서 여전히 문제가 있었다.
결국 JavaScript로 실제 viewport 높이를 감지하는 방식을 추가했다. window.innerHeight를 사용해서 실제 가용 높이를 계산하고, CSS 커스텀 프로퍼티로 주입하는 방법이다. resize 이벤트와 visualViewport API를 함께 활용해서 키보드가 올라올 때도 대응했다.
Claude에게 "Chrome 모바일에서 하단 고정 요소의 위치가 브라우저 UI 변화에 영향받는 문제를 해결하고 싶다"고 했을 때, 여러 접근법을 제안받았다. 하지만 어떤 방법이 실제로 동작하는지는 직접 모바일 기기에서 테스트하지 않으면 알 수 없었다. AI가 제안하는 해결책이 이론적으로는 맞지만 실제 디바이스에서 다르게 동작하는 경우가 많다는 걸, 이 버그 해결 과정에서 뼈저리게 느꼈다.
터치 인터페이스: hover가 없는 세계
데스크톱에서는 마우스 hover가 강력한 피드백 수단이다. 버튼 위에 마우스를 올리면 색이 변하고, 카드 위에 올리면 확대되고. 하지만 모바일에는 hover가 없다. 손가락이 화면에 닿는 순간은 이미 "터치"이지 "올려놓기"가 아니다.
hover에 의존하던 인터랙션을 모두 재설계해야 했다. 카드의 hover 확대 효과는 터치 시 순간적으로 확대됐다가 뒤집어지는 애니메이션으로 대체했다. 툴팁은 길게 누르기(long press)로 변경했다. 버튼의 hover 색상 변경은 active 상태(누르는 순간)의 스타일로 바꿨다.
터치 타겟 크기도 중요한 문제였다. 애플과 구글 모두 최소 터치 타겟을 44x44 포인트로 권장한다. 데스크톱에서는 작은 아이콘 버튼이 문제없지만, 손가락으로 누르기에는 너무 작다. 카드 선택, 메뉴 버튼, 공유 아이콘 등 모든 인터랙티브 요소의 최소 크기를 확보하는 작업을 했다.
스크롤 경험도 신경 써야 했다. 카드 목록을 좌우로 스와이프하는 동작이 브라우저의 뒤로 가기 제스처와 충돌하는 문제가 있었다. 화면 가장자리에서 시작되는 스와이프는 브라우저에 양보하고, 화면 중앙에서 시작되는 스와이프만 카드 네비게이션으로 처리하는 식의 세밀한 조정이 필요했다.
반응형 디자인의 현실: "줄이기"만으로는 부족하다
처음에는 반응형 디자인을 단순하게 생각했다. 큰 화면의 레이아웃을 작은 화면에 맞게 줄이면 되는 것 아닌가? 현실은 그렇지 않았다.
데스크톱에서 카드 3장을 가로로 나란히 보여주는 쓰리카드 레이아웃은 모바일에서 그대로 줄이면 카드가 너무 작아진다. 세로로 쌓으면 한 화면에 다 안 보인다. 결국 모바일에서는 카드를 슬라이드 형태로 좌우 스와이프하면서 하나씩 보는 별도의 레이아웃을 만들었다.
켈틱 크로스 스프레드의 10장 레이아웃은 더 심각했다. 데스크톱에서는 전통적인 배치(중앙에 십자 형태, 옆에 네 장)를 보여줄 수 있지만, 모바일에서는 불가능하다. 모바일 전용 레이아웃을 별도로 설계해서 스크롤 가능한 리스트 형태로 변환했다. 같은 정보를 다른 형태로 보여주는 것이다.
이 경험에서 배운 건, 진정한 반응형 디자인은 "레이아웃을 줄이는 것"이 아니라 "각 화면 크기에 최적화된 경험을 설계하는 것"이라는 점이다. 미디어 쿼리 하나로 해결되는 문제가 아니라, 때로는 완전히 다른 컴포넌트를 렌더링해야 하는 문제다.
Linear 스타일 UI 개편: 모던하고 클린한 디자인으로
기능을 추가하면서 UI가 점점 복잡해졌다. 버튼이 늘어나고, 정보가 많아지면서 화면이 어수선해졌다. 이때 Linear의 UI에서 영감을 받아 전면 개편을 결정했다.
Linear는 프로젝트 관리 도구인데, UI가 놀라울 정도로 깔끔하다. 불필요한 요소를 극도로 절제하고, 여백을 과감하게 사용하며, 미니멀한 아이콘과 부드러운 애니메이션으로 고급스러움을 표현한다. 이 철학을 타로 마스터에 적용하기로 했다.
구체적으로 바뀐 것은 이렇다. 둥근 모서리의 카드형 레이아웃에서 직선적이고 날렵한 디자인으로. 화려한 그라데이션 버튼에서 미니멀한 아웃라인 버튼으로. 꽉 찬 레이아웃에서 충분한 여백이 있는 호흡 있는 레이아웃으로. 각 변경은 작지만, 전체적인 인상이 "아마추어 사이드 프로젝트"에서 "제대로 만든 프로덕트"로 확 바뀌었다.
Claude에게 "Linear 스타일의 미니멀한 UI로 리디자인하고 싶다"고 했을 때, 여백 비율, 폰트 사이즈 스케일, 색상 사용 최소화 같은 구체적인 가이드라인을 받았다. 하지만 실제로 적용하면서 "이 정도 여백이면 너무 비어 보이지 않나?" 하는 감각적 판단은 결국 내가 해야 했다. 디자인은 규칙만으로 완성되지 않고, 규칙 위의 감각이 필요한 영역이기 때문이다.
모바일 사용자가 대부분인 현실
타로 마스터의 사용 데이터를 보면 모바일 비율이 압도적이다. 이건 타로 마스터만의 특성이 아니라 대부분의 콘텐츠 기반 웹 서비스의 현실이다. 특히 타로처럼 개인적이고 가볍게 즐기는 콘텐츠는 모바일 비율이 더욱 높다.
이 현실을 인정하면 우선순위가 바뀐다. "데스크톱에서 만들고 모바일에 맞추기"가 아니라 "모바일을 기준으로 만들고 데스크톱으로 확장하기"가 올바른 순서다. 소위 모바일 퍼스트 접근이다. 말로는 많이 들었지만, 실제로 이렇게 개발하려면 의식적인 전환이 필요하다.
개발 과정에서도 변화가 필요했다. 데스크톱 모니터의 넓은 화면에서 코드를 쓰면서, Chrome 개발자 도구의 모바일 뷰가 아니라 실제 스마트폰에서 수시로 확인하는 습관을 들였다. 개발자 도구의 모바일 시뮬레이션은 많은 부분을 재현하지만, 실제 디바이스의 터치 반응, 스크롤 관성, 키보드 동작까지 완벽하게 재현하지는 못한다.
모바일 최적화에서 배운 것
모바일 최적화 작업을 통해 배운 가장 큰 교훈은, 모바일 대응이 "추가 작업"이 아니라 "기본 작업"이라는 것이다. 프로젝트 초기부터 모바일을 기준으로 설계했다면 나중에 들인 시간의 절반은 아낄 수 있었을 것이다.
AI는 모바일 최적화에서도 도움이 됐지만, 한계도 명확했다. CSS 속성과 레이아웃 패턴은 잘 제안해주지만, "이 화면이 실제 스마트폰에서 어떻게 느껴지는지"를 판단하는 건 불가능하다. 터치할 때의 반응 속도, 스크롤의 매끄러움, 화면 전환의 자연스러움. 이런 감각적 검증은 결국 사람의 손끝에서만 이루어진다.
결국 모바일 최적화는 코드의 문제가 아니라 경험의 문제다. 코드는 도구이고, 그 도구로 어떤 경험을 만들 것인가에 대한 판단이 핵심이다. 그리고 그 판단은 실제 디바이스를 손에 들고 직접 사용해봐야만 내릴 수 있다.
다음 편 예고
기능도 만들었고, 모바일 최적화도 했다. 하지만 이 모든 과정에서 한 가지 중요한 질문이 남아 있다. AI가 만들어준 코드를 정말 이해하고 있는가? 다음 편에서는 "바이브 코딩"의 함정과, 내가 왜 "의도 기반 협업"이라는 다른 방식을 선택했는지를 이야기한다.
댓글
댓글 쓰기