Files
sam-react-prod/claudedocs/guides/mobile/[GUIDE] foldable-device-layout-fix.md
유병철 07374c826c refactor(WEB): claudedocs 재정리 및 AuthContext/Zustand/유틸 코드 개선
- claudedocs 폴더 구조 재정리: archive/sessions, guides/migration·mobile·universal-list, refactoring 분류
- 오래된 세션 컨텍스트/체크리스트 문서 정리 (아카이브 이동 또는 삭제)
- AuthContext → authStore(Zustand) 전환 시작, RootProvider 간소화
- GenericCRUDDialog 공통 다이얼로그 컴포넌트 추가
- PermissionDialog 삭제 → GenericCRUDDialog로 대체
- RankDialog/TitleDialog GenericCRUDDialog 기반으로 리팩토링
- toast-utils.ts 삭제 (미사용)
- fileDownload.ts 개선, excel-download.ts 정리
- menuStore/themeStore Zustand 셀렉터 최적화
- useColumnSettings/useTableColumnStore 기능 보강
- 세금계산서/견적/작업자화면/결재 등 소규모 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:17:13 +09:00

4.6 KiB

폴더블 기기(Galaxy Fold) 레이아웃 대응 가이드

작성일: 2026-01-09 적용 파일: AuthenticatedLayout.tsx, globals.css


문제 현상

Galaxy Fold 같은 폴더블 기기에서 넓은 화면 ↔ 좁은 화면 전환 시:

  • 사이트 너비가 정확히 계산되지 않음
  • 전체 레이아웃이 틀어짐
  • 화면 전환 후에도 이전 크기가 유지됨

원인 분석

1. window.innerWidth의 한계

// 기존 코드
window.addEventListener('resize', () => {
  setIsMobile(window.innerWidth < 768);
});
  • 폴더블 기기에서 화면 전환 시 window.innerWidth 값이 즉시 업데이트되지 않음
  • resize 이벤트가 불완전하게 발생

2. CSS 100vh / 100vw 문제

/* 기존 */
height: 100vh;  /* h-screen */
  • Tailwind의 h-screen100vh로 계산됨
  • 폴더블 기기에서 viewport units가 늦게 재계산되어 레이아웃 깨짐

해결 방법

1. visualViewport API 사용

window.visualViewport는 실제 보이는 viewport 크기를 더 정확하게 반환합니다.

// src/layouts/AuthenticatedLayout.tsx

useEffect(() => {
  const updateViewport = () => {
    // visualViewport API 우선 사용 (폴더블 기기에서 더 정확)
    const width = window.visualViewport?.width ?? window.innerWidth;
    const height = window.visualViewport?.height ?? window.innerHeight;

    setIsMobile(width < 768);

    // CSS 변수로 실제 viewport 크기 설정
    document.documentElement.style.setProperty('--app-width', `${width}px`);
    document.documentElement.style.setProperty('--app-height', `${height}px`);
  };

  updateViewport();

  // resize 이벤트
  window.addEventListener('resize', updateViewport);

  // visualViewport resize 이벤트 (폴드 전환 감지)
  window.visualViewport?.addEventListener('resize', updateViewport);

  return () => {
    window.removeEventListener('resize', updateViewport);
    window.visualViewport?.removeEventListener('resize', updateViewport);
  };
}, []);

2. CSS 변수 + dvw/dvh fallback

/* src/app/[locale]/globals.css */

:root {
  /* 폴더블 기기 대응 - JS에서 동적으로 업데이트됨 */
  --app-width: 100vw;
  --app-height: 100vh;

  /* dvh/dvw fallback (브라우저 지원 시 자동 적용) */
  --app-height: 100dvh;
  --app-width: 100dvw;
}
단위 설명
vh/vw 초기 viewport 기준 (고정)
dvh/dvw Dynamic viewport - 동적으로 변함
svh/svw Small viewport - 최소 크기 기준
lvh/lvw Large viewport - 최대 크기 기준

3. 레이아웃에서 CSS 변수 사용

// 기존: h-screen (100vh 고정)
<div className="h-screen flex flex-col">

// 변경: CSS 변수 사용 (동적 업데이트)
<div className="flex flex-col" style={{ height: 'var(--app-height)' }}>

작동 원리

┌─────────────────────────────────────────────────────┐
│  폴드 전환 발생                                       │
│       ↓                                             │
│  visualViewport resize 이벤트 발생                   │
│       ↓                                             │
│  updateViewport() 실행                              │
│       ↓                                             │
│  CSS 변수 업데이트 (--app-width, --app-height)       │
│       ↓                                             │
│  레이아웃 즉시 재계산                                 │
└─────────────────────────────────────────────────────┘

브라우저 지원

API/속성 Chrome Safari Firefox Samsung Internet
visualViewport 61+ 13+ 91+ 8.0+
dvh/dvw 108+ 15.4+ 101+ 21+
  • visualViewport 미지원 시 → window.innerWidth/Height fallback
  • dvh/dvw 미지원 시 → JS에서 계산한 값으로 대체

관련 파일

파일 역할
src/layouts/AuthenticatedLayout.tsx viewport 감지 및 CSS 변수 업데이트
src/app/[locale]/globals.css CSS 변수 선언 및 fallback

참고 자료