본문 바로가기
기술의기록

모바일 웹 개발자라면 꼭 알아야 할 CSS 새 뷰포트 단위 3가지 (dvh, svh, lvh 완벽 정리)

by Jeremy Winchester 2025. 9. 30.
반응형

웹 개발하면서 이런 경험 있으신가요? PC에서는 완벽하게 동작하던 100vh 설정이 모바일에서 확인해보니 스크롤이 생기거나 화면이 잘려보이는 문제 말이에요. 특히 iOS Safari나 카카오톡 인앱 브라우저에서 이런 현상이 자주 발생하죠.

저도 프로젝트를 배포하고 나서 이 문제로 한참을 고민했던 기억이 있어요. 주소창과 하단 네비게이션 바 때문에 레이아웃이 망가지는 걸 보면서 "분명 개발자 도구에서는 문제없었는데..."라고 생각했거든요. 😅

다행히도 CSS에는 이제 이런 문제를 해결할 수 있는 3가지 새로운 뷰포트 단위가 추가되었습니다. 오늘은 이 단위들을 실무에서 어떻게 활용할 수 있는지 상세하게 알려드릴게요!

🤔 먼저, 왜 100vh가 모바일에서 문제가 될까요?

본격적으로 새로운 단위를 알아보기 전에, 기존 vh 단위의 문제점부터 이해하고 넘어가는 게 중요해요.

vh(Viewport Height)의 기본 개념

vh는 "Viewport Height"의 약자로, 브라우저 뷰포트 높이의 1%를 의미하는 상대 단위입니다. 예를 들어 화면 높이가 1000px이면 1vh는 10px이 되는 거죠. 따라서 100vh는 이론상 전체 화면 높이와 같아야 합니다.

모바일 브라우저의 특별한 동작 방식

문제는 모바일 브라우저에서 발생합니다. 모바일 브라우저들은 사용자 경험을 위해 동적 UI 요소를 가지고 있어요:

  • 상단 주소창 (URL Bar): 스크롤하면 사라졌다 나타났다 함
  • 하단 네비게이션 바: 브라우저 컨트롤 버튼들
  • 탭 바: iOS Safari의 경우 하단 탭 바

여기서 핵심 문제가 발생합니다. 기존 vh 단위는 이런 동적 UI 요소들이 완전히 숨겨진 상태의 최대 화면 높이를 기준으로 계산됩니다. 그래서 주소창이 표시된 상태에서는 실제 보이는 영역보다 큰 값이 적용되어 스크롤이 생기는 거예요.

실제로 iOS Safari에서 주소창이 보이는 상태에서 100vh를 적용하면:

  • 상단: 주소창 아래로 콘텐츠가 가려짐
  • 하단: 하단 바 아래로 콘텐츠가 잘림
  • 결과: 불필요한 스크롤 발생 😭

🚀 CSS 새로운 뷰포트 단위 3형제 등장!

이런 문제를 해결하기 위해 CSS Working Group은 3가지 새로운 뷰포트 단위를 도입했습니다. 각각의 특성을 자세히 알아볼까요?

1️⃣ lvh (Large Viewport Height) - 최대 뷰포트 높이

lvh는 "Large Viewport Height"의 약자로, 브라우저 UI가 완전히 숨겨졌을 때의 최대 화면 높이를 의미합니다.

특징

  • 툴바, 주소창 등이 모두 사라진 상태의 높이
  • 값이 고정되어 있어 안정적
  • 기존 vh와 거의 동일한 동작

언제 사용할까?

  • 전체 화면을 활용하는 히어로 섹션
  • 스크롤 후 전체 화면으로 확장되는 콘텐츠
  • 모달이나 오버레이가 최대한 공간을 활용해야 할 때
.hero-section {
  height: 100lvh;
  /* 브라우저 UI가 숨겨진 상태 기준으로 전체 화면 */
}

2️⃣ svh (Small Viewport Height) - 최소 뷰포트 높이

svh는 "Small Viewport Height"의 약자로, 브라우저 UI가 모두 표시된 상태의 최소 화면 높이를 의미합니다.

특징

  • 주소창, 하단 바 등이 모두 보이는 상태의 높이
  • 가장 안전하고 예측 가능한 단위
  • 값이 고정되어 레이아웃 점프 없음

언제 사용할까?

  • 항상 화면에 보이는 고정 요소 (헤더, 푸터)
  • 스크롤 없이 모든 콘텐츠를 보여줘야 하는 섹션
  • 레이아웃 안정성이 최우선일 때
.fixed-header {
  height: 10svh;
  position: fixed;
  /* 항상 표시되는 UI를 고려한 안전한 높이 */
}

.content-wrapper {
  height: 100svh;
  /* 브라우저 UI가 있어도 스크롤 없이 전체 표시 */
}

3️⃣ dvh (Dynamic Viewport Height) - 동적 뷰포트 높이

dvh는 "Dynamic Viewport Height"의 약자로, 현재 브라우저 상태에 따라 실시간으로 변하는 높이를 의미합니다.

특징

  • 브라우저 UI 표시 여부에 따라 동적으로 변함
  • 주소창이 나타나면 svh처럼, 사라지면 lvh처럼 동작
  • 가장 유연하지만 주의가 필요

언제 사용할까?

  • 스크롤에 따라 자연스럽게 확장되는 콘텐츠
  • 사용자 인터랙션에 반응하는 레이아웃
  • 부드러운 전환 효과가 필요한 섹션
.dynamic-section {
  height: 100dvh;
  /* 현재 보이는 화면에 딱 맞게 자동 조절 */
}

⚠️ dvh 사용 시 꼭 알아야 할 주의사항!

dvh는 가장 똑똑해 보이지만, 실무에서는 예상치 못한 문제를 일으킬 수 있어요.

레이아웃 점프 문제

다음과 같은 상황을 상상해보세요:

.section-1, .section-2, .section-3 {
  height: 100dvh;
}
  1. 사용자가 3번째 섹션까지 스크롤해서 내려갑니다
  2. 하단의 버튼을 클릭하려고 합니다
  3. 살짝 위로 스크롤하자 주소창이 나타납니다
  4. 모든 섹션의 높이가 줄어듭니다!
  5. 클릭하려던 버튼이 갑자기 아래로 이동합니다 😱

이런 현상을 "레이아웃 점프(Layout Jump)"라고 하는데, 사용자 경험을 크게 해치는 요소예요. 특히 클릭 직전에 요소가 움직이면 정말 짜증나죠.

성능 문제

dvh는 스크롤할 때마다 높이를 재계산하기 때문에 많은 요소에 사용하면 성능 저하가 발생할 수 있습니다. 특히 복잡한 레이아웃에서는 더욱 신중하게 사용해야 해요.

💡 실무에서 현명하게 사용하는 방법

그렇다면 실제 프로젝트에서는 어떤 단위를 선택해야 할까요? 상황별 추천 전략을 정리해봤어요.

📱 모바일 랜딩 페이지

/* 첫 화면 히어로 섹션 */
.hero {
  height: 100svh; /* 안전하게 항상 보이는 영역만 사용 */
  display: flex;
  justify-content: center;
  align-items: center;
}

/* 스크롤 후 나타나는 콘텐츠 섹션 */
.content-section {
  min-height: 100lvh; /* 스크롤 후 전체 화면 활용 */
  padding: 2rem;
}

🎯 풀스크린 모달/팝업

.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100dvh; /* 현재 보이는 화면에 딱 맞게 */
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-content {
  max-height: 90dvh; /* 약간의 여백 확보 */
  overflow-y: auto;
}

🎨 인터랙티브 스크롤 효과

/* 배경 이미지가 스크롤에 따라 확장 */
.parallax-bg {
  height: 100dvh; /* 동적으로 변하는 효과 */
  background-attachment: fixed;
  transition: height 0.3s ease; /* 부드러운 전환 */
}

/* 고정 헤더는 안정적으로 */
.header {
  height: 8svh;
  position: fixed;
  top: 0;
}

🛡️ 안전한 폴백(Fallback) 패턴

새로운 단위를 지원하지 않는 구형 브라우저를 위한 폴백 코드:

.full-screen-section {
  /* 1단계: 구형 브라우저를 위한 기본값 */
  height: 100vh;
  
  /* 2단계: 새로운 단위 지원 시 덮어쓰기 */
  height: 100dvh;
}

/* 또는 @supports로 명시적 처리 */
.section {
  height: 100vh;
}

@supports (height: 100dvh) {
  .section {
    height: 100dvh;
  }
}

📊 브라우저 지원 현황 (2025년 기준)

좋은 소식은 대부분의 최신 브라우저가 이미 이 단위들을 지원한다는 거예요!

✅ 완벽 지원

  • Chrome/Edge: 108 버전 이상
  • Safari: 15.4 버전 이상
  • Firefox: 101 버전 이상
  • Samsung Internet: 21.0 버전 이상

⚠️ 부분 지원

  • Opera: 94 버전 이상

❌ 미지원

  • Internet Explorer: 모든 버전 (이미 지원 종료)
  • 구형 Android 브라우저: 5.0 이하

호환성 요약: 전 세계 사용자의 약 94% 이상이 이 단위들을 지원하는 브라우저를 사용하고 있어요. Can I Use 통계에 따르면 매우 안전하게 사용할 수 있는 수준입니다!

🔧 기존 프로젝트 마이그레이션 가이드

이미 vh를 사용 중인 프로젝트를 새로운 단위로 전환하는 단계별 방법입니다.

Step 1: 문제 파악하기

먼저 어떤 요소에서 모바일 문제가 발생하는지 확인하세요:

// 개발자 도구 콘솔에서 실행
console.log('Viewport Height:', window.innerHeight);
console.log('VH in pixels:', window.innerHeight / 100);

// 실제 렌더링된 요소의 높이 확인
const element = document.querySelector('.your-section');
console.log('Element Height:', element.offsetHeight);

Step 2: 단위 선택하기

각 요소의 목적에 맞는 단위를 선택합니다:

/* Before */
.hero { height: 100vh; }
.fixed-header { height: 10vh; }
.modal { height: 100vh; }

/* After */
.hero { 
  height: 100vh; /* 폴백 */
  height: 100svh; /* 안정적인 첫 화면 */
}

.fixed-header { 
  height: 10vh; /* 폴백 */
  height: 10svh; /* 항상 보이는 헤더 */
}

.modal { 
  height: 100vh; /* 폴백 */
  height: 100dvh; /* 현재 화면에 맞춤 */
}

Step 3: 테스트하기

꼭 실제 기기에서 테스트하세요! 개발자 도구의 모바일 모드는 주소창 동작을 완벽하게 재현하지 못합니다.

테스트 체크리스트:

  • [ ] iOS Safari에서 스크롤 전/후 확인
  • [ ] Android Chrome에서 주소창 숨김/표시 확인
  • [ ] 카카오톡 인앱 브라우저 테스트
  • [ ] 가로/세로 모드 전환 테스트
  • [ ] 다양한 화면 크기 테스트

🎓 실전 예제: 완벽한 모바일 랜딩 페이지

이제 배운 내용을 종합해서 실전 예제를 만들어볼게요:

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    /* 고정 헤더 - 항상 보이므로 svh */
    .header {
      height: 8svh;
      height: 8vh; /* 폴백 */
      height: 8svh;
      position: fixed;
      top: 0;
      width: 100%;
      background: #fff;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
      z-index: 100;
    }

    /* 첫 화면 - 안전하게 svh */
    .hero {
      height: 100vh; /* 폴백 */
      height: 100svh;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      text-align: center;
      padding: 2rem;
    }

    /* 콘텐츠 섹션 - 자연스러운 확장을 위해 lvh */
    .content-section {
      min-height: 100vh; /* 폴백 */
      min-height: 100lvh;
      padding: 4rem 2rem;
      display: flex;
      flex-direction: column;
      justify-content: center;
    }

    /* CTA 섹션 - 현재 화면에 딱 맞게 dvh */
    .cta-section {
      height: 100vh; /* 폴백 */
      height: 100dvh;
      display: flex;
      justify-content: center;
      align-items: center;
      background: #f7fafc;
    }

    /* 모달 - 동적으로 조절되는 dvh */
    .modal {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh; /* 폴백 */
      height: 100dvh;
      background: rgba(0, 0, 0, 0.8);
      z-index: 1000;
    }

    .modal.active {
      display: flex;
      justify-content: center;
      align-items: center;
    }
  </style>
</head>
<body>
  <header class="header">
    <!-- 헤더 내용 -->
  </header>

  <section class="hero">
    <h1>새로운 뷰포트 단위로<br>완벽한 모바일 경험</h1>
    <p>svh, lvh, dvh로 모든 기기에서 완벽하게</p>
  </section>

  <section class="content-section">
    <!-- 콘텐츠 -->
  </section>

  <section class="cta-section">
    <!-- CTA 버튼 -->
  </section>
</body>
</html>

🆚 기존 JavaScript 해결법과 비교

예전에는 모바일 vh 문제를 JavaScript로 해결했었죠. 두 방법을 비교해볼게요.

JavaScript 방식 (구식)

// 복잡하고 성능 문제 있음
function setVh() {
  let vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}

setVh();
window.addEventListener('resize', setVh);
window.addEventListener('orientationchange', setVh);
.section {
  height: calc(var(--vh, 1vh) * 100);
}

CSS 새 단위 방식 (신식) ✅

.section {
  height: 100vh; /* 폴백 */
  height: 100svh; /* 한 줄로 끝! */
}

신식 방법의 장점:

  • ✨ 코드가 훨씬 간결함
  • 🚀 JavaScript 실행 없이 순수 CSS로 처리
  • ⚡ 성능이 더 좋음
  • 🔧 유지보수가 쉬움
  • 📱 브라우저 최적화 혜택
  •  

출처 : https://www.linkedin.com/posts/utsavmeena_3-new-viewport-units-in-css-explained-activity-7377712991494127616-WyUQ?utm_source=share&utm_medium=member_desktop&rcm=ACoAADmRDq8BrjkgLhfPgIc-Upf0JJ0quleqBQg

 

📝 정리하며

모바일 웹 개발에서 뷰포트 높이 문제는 정말 오랫동안 개발자들을 괴롭혀온 이슈였어요. 저도 처음에는 JavaScript로 해결하다가, position: fixed로 꼼수를 부리기도 하고, 결국엔 "모바일에서는 그냥 스크롤 생기는 걸로..."라고 타협했던 적도 있었거든요. 😅

하지만 이제는 CSS만으로 깔끔하게 해결할 수 있습니다!

핵심 정리

  • svh: 안전하고 예측 가능 → 고정 요소, 첫 화면
  • lvh: 최대 공간 활용 → 스크롤 후 콘텐츠, 히어로 섹션
  • dvh: 유연하지만 주의 필요 → 모달, 인터랙티브 요소

실무 적용 팁

  1. 기본은 svh로 시작하세요 (가장 안전)
  2. 필요한 곳에만 lvh, dvh 사용
  3. 항상 vh 폴백을 먼저 작성
  4. 실제 기기에서 꼭 테스트!
반응형