본문 바로가기
사이드프로젝트

[개선] 모바일 뷰포트 수정과 직관적인 지도 검색

by geoyeon-ai 2026. 2. 18.

오늘 개발의 핵심 주제는 "사용자가 보고 있는 곳을 중심으로" 였습니다. 모바일 환경에서의 화면 높이 문제 해결부터, 내 위치가 아닌 내가 보고 있는 지도 중심으로 검색하는 경험까지, 사용자 경험(UX)을 한 단계 끌어올린 과정을 정리해 봅니다.


1. 100vh의 배신, 그리고 100dvh로의 전환

모바일 웹 개발자라면 누구나 한 번쯤 겪는 고통이 있습니다. 바로 브라우저 주소창(Address Bar)입니다.

기존에는 min-h-screen이나 100vh를 사용해 전체 화면을 잡으려 했지만, 모바일 사파리 등에서는 주소창이 나타나거나 사라질 때 뷰포트 크기가 달라지며 하단 버튼이 가려지거나 불필요한 스크롤이 생기는 문제가 발생했습니다. 특히 지도 서비스에서는 하단 푸터나 줌 버튼이 가려지는 치명적인 UX 저하가 있었습니다.

해결책: Dynamic Viewport Height (dvh)

우리는 과감하게 레이아웃 전략을 수정했습니다.

// Before: 불확실한 높이 계산
<div className="min-h-screen flex flex-col ...">
  <div className="h-[calc(100vh-120px)]">...</div> // 위험한 하드코딩

// After: 완벽한 뷰포트 제어
<div className="h-dvh flex flex-col overflow-hidden ...">
    <header>...</header>
    <main className="flex-1 relative min-h-0">
        <!-- 지도가 남은 공간을 100% 채움 -->
        <KakaoMap className="h-full w-full" />
    </main>
    <footer>...</footer>
</div>

h-dvh를 도입하여 브라우저 UI의 변화에 즉각적으로 대응하는 높이 값을 사용했고, flex-col 구조 안에서 flex-1을 사용해 지도가 "남는 공간을 정확히 채우도록" 했습니다. 이제 더 이상 calc(100vh - 60px) 같은 매직 넘버를 추측할 필요가 없습니다.


2. "여기서 찾아줘": 지도 중심 기반 검색

기존의 검색은 "내 디바이스 위치" 기준이었습니다. 하지만 사용자는 내가 있는 곳보다 "내가 이사 갈 곳", "부모님이 계신 곳"을 궁금해합니다. 지도를 아무리 강남으로 옮겨도 검색 결과는 여전히 내가 있는 분당을 가리키고 있다면, 그것은 좋은 경험이 아닙니다.

우리는 "현재 지도 위치에서 조회" 기능을 도입했습니다.

전체 흐름도

sequenceDiagram
    participant U as 사용자
    participant P as page.tsx
    participant M as KakaoMap

    P->>U: 위치 권한 요청
    alt 위치 허용
        U-->>P: userLocation 반환
        P->>M: center = userLocation
    else 위치 거부
        P->>M: center = 강남구 기본좌표
    end
    M-->>P: onCenterChanged(center) 최초 호출
    P->>P: loadData(center) (시설 목록 조회)
    Note over P,M: 유저가 지도를 드래그하면
    M-->>P: onCenterChanged(newCenter)
    P->>P: hasMoved = true (버튼 표시)
    U->>P: "현재 위치에서 조회" 클릭
    P->>P: loadData(newCenter) (재조회)
    P->>P: hasMoved = false (버튼 숨김)

기술적 구현: idle 이벤트의 활용

사용자가 지도를 드래그하는 동안 계속 API를 호출할 수는 없습니다. 지도 이동이 완전히 끝난 시점을 알아채야 했습니다. 카카오맵의 idle 이벤트가 정답이었습니다.

// KakaoMap.tsx
window.kakao.maps.event.addListener(newMap, 'idle', () => {
    const center = newMap.getCenter();
    // 중심 좌표가 변경되었음을 부모 컴포넌트에 알림
    onCenterChanged?.({ lat: center.getLat(), lng: center.getLng() });
});

그리고 메인 페이지에서는 이 상태를 감지하여 플로팅 버튼을 띄웁니다.

// page.tsx
const handleCenterChanged = (center) => {
    setMapCenterForSearch(center);
    if (!isInitialLoad) {
        setHasMoved(true); // "현재 위치에서 조회" 버튼 등장!
    }
};

3. 맥락의 유지: "왜 자꾸 딴 데로 가?"

기능을 구현하고 테스트하던 중, 한 가지 어색한 점을 발견했습니다.

"강남역을 보고 싶어서 지도를 옮기고 '조회'를 눌렀는데, 갑자기 지도가 목록 첫 번째 요양원 위치(약간 떨어진 곳)로 훅 이동해버린다."

이전 로직에는 "검색 후 첫 번째 결과로 지도 중심 이동" 코드가 있었습니다. 이는 초기 진입 시에는 유용했지만, 사용자가 의도를 가지고 지도를 이동시킨 후에는 "내가 보고 있던 곳"을 빼앗는 방해 요소가 되었습니다.

우리는 이 자동 이동 로직을 과감히 제거했습니다.

- // 검색 후 첫 번째 결과로 이동 (삭제)
- setMapCenter({ lat: firstItem.lat, lng: firstItem.lng });

이제 사용자는 검색 후에도 자신이 보고 있던 지도 영역을 그대로 유지하며 주변 시설들을 탐색할 수 있습니다. 작은 변화지만, 탐색의 흐름이 훨씬 자연스러워졌습니다.


4. 마무리

오늘의 작업은 단순히 기능을 붙이는 것이 아니라, "모바일 웹에서 네이티브 앱 같은 자연스러운 경험"을 만드는 과정이었습니다.

  1. dvh로 뷰포트 이슈를 원천 차단하고,
  2. idle 이벤트로 지도 중심 검색의 기술적 토대를 마련했으며,
  3. 사용자의 탐색 맥락을 존중하는 UX 디테일까지 챙겼습니다.

이제 시니어 숲은 내가 서 있는 곳뿐만 아니라, 내가 바라보는 곳의 정보도 가장 편안하게 전달해줍니다. 다음은 목록 및 검색을 강화하겠습니다.