/* 픽셀 단위의 문제점 */
.section {
height: 600px; /* 모든 화면에서 항상 600px */
}
/* 결과: */
/* 800px 화면: 600px = 화면의 75% 차지 (적당함) */
/* 400px 화면: 600px = 화면보다 큼 (넘침) */
/* 1200px 화면: 600px = 화면의 50% (너무 작음) */
/* vh 단위의 장점 */
.section {
height: 75vh; /* 모든 화면에서 항상 화면의 75% */
}
/* 결과: */
/* 800px 화면: 75vh = 600px (75%) */
/* 400px 화면: 75vh = 300px (75%) */
/* 1200px 화면: 75vh = 900px (75%) */
// 1vh = 화면 높이의 기본 단위 (1%)
// 다른 vh 값들은 1vh의 배수로 계산
1vh = 화면의 1%
10vh = 1vh × 10 = 화면의 10%
50vh = 1vh × 50 = 화면의 50%
100vh = 1vh × 100 = 화면의 100%
300vh = 1vh × 300 = 화면의 300%
브라우저 창 전체
┌─────────────────────────┐ ← window.outerHeight (전체 브라우저)
│ 제목표시줄 │
├─────────────────────────┤
│ 주소창, 탭바, 도구모음 │ ← 이 부분들 제외
├─────────────────────────┤ ↑
│ │ │
│ 실제 콘텐츠 영역 │ │ window.innerHeight
│ (viewport) │ │ = 100vh
│ │ │ (실제 웹페이지 보이는 부분)
│ │ │
└─────────────────────────┘ ↓
// viewport = 실제 웹 콘텐츠가 렌더링되는 영역
console.log('전체 브라우저 높이:', window.outerHeight); // 900px
console.log('viewport 높이:', window.innerHeight); // 800px
console.log('차이:', window.outerHeight - window.innerHeight); // 100px (UI 요소들)
// 100vh = window.innerHeight = viewport 높이
// 현재 브라우저에서 실행해보세요
console.log('=== 브라우저 크기 측정 ===');
console.log('전체 브라우저 높이:', window.outerHeight);
console.log('viewport 높이 (100vh):', window.innerHeight);
console.log('주소창+탭바 높이:', window.outerHeight - window.innerHeight);
// 예시 결과:
// 전체 브라우저 높이: 900
// viewport 높이 (100vh): 820
// 주소창+탭바 높이: 80
<!-- 100vh 테스트 -->
<div style="height: 100vh; background: red; border: 2px solid black;">
이 빨간 박스가 viewport 전체 높이 (100vh)
</div>
<!-- 결과: 스크롤 없이 화면에 딱 맞게 표시됨 -->
ScrollTrigger.create({
trigger: introCard,
start: "top top",
end: "+=300vh", // 화면 3개 분량의 스크롤
onUpdate: (self) => {
[1번째 화면] 100vh - 이미지가 작고 둥근 상태
↓ 스크롤
[2번째 화면] 100vh - 이미지가 점점 커지면서 변형
↓ 스크롤
[3번째 화면] 100vh - 이미지가 최대 크기, 사각형으로 완성
↓
애니메이션 완료
// 100vh: 너무 빠름 (1번 스크롤로 끝)
// 200vh: 적당하지만 약간 빠름
// 300vh: 충분히 천천히 변화 관찰 가능 ✓
// 500vh: 너무 김 (사용자가 지루해함)
/* 모든 섹션을 화면 높이에 맞춤 */
.hero-section { height: 100vh; } /* 첫 화면 전체 */
.content-section { height: 80vh; } /* 화면의 80% */
.footer-section { height: 20vh; } /* 화면의 20% */
아이폰 (844px):
- hero: 844px
- content: 675px
- footer: 169px
데스크탑 (1080px):
- hero: 1080px
- content: 864px
- footer: 216px
→ 비율은 동일하지만 실제 크기는 화면에 맞게 조절
// ScrollTrigger에서 진행률별 계산
onUpdate: (self) => {
const progress = self.progress; // 0.0 ~ 1.0
// progress와 vh를 조합한 정밀한 계산
let currentVH = window.innerHeight / 100; // 1vh 크기
let animationDistance = currentVH * 300; // 300vh 거리
let currentPosition = animationDistance * progress;
// 예: progress 0.5일 때
// currentPosition = (10.8px × 300) × 0.5 = 1620px
}
// 구형 브라우저에서 vh 지원 안 할 때 폴백
function getVH(amount) {
if (CSS.supports('height', '1vh')) {
return `${amount}vh`; // 모던 브라우저
} else {
return `${(window.innerHeight / 100) * amount}px`; // 폴백
}
}
console.log(getVH(300)); // "300vh" 또는 "2400px"
// iOS Safari의 문제: 주소창이 사라지면 vh가 변함
초기 상태 (주소창 있음): 100vh = 635px
스크롤 후 (주소창 없음): 100vh = 695px
// 해결책: 실제 높이 고정
function setVH() {
let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
window.addEventListener('resize', setVH);
setVH();
/* 안정적인 vh 사용법 */
.full-height {
height: 100vh; /* 폴백 */
height: calc(var(--vh, 1vh) * 100); /* 정확한 값 */
}
TV 화면 (전체 기기) vs 실제 영상 영역 (viewport)
┌─────────────────────┐ ← TV 전체 (outerHeight)
│ [전원] [음량] [채널] │ ← 버튼들 (브라우저 UI)
├─────────────────────┤
│ │ ← 실제 영상 (viewport = 100vh)
│ 영화 화면 영역 │
│ │
└─────────────────────┘
이제 vh 단위의 목적과 100vh의 정확한 의미를 이해하셨나요?