
미디어 쿼리 없이 반응형 레이아웃을 만들 수 있다면 믿어지십니까? 저도 처음엔 반신반의했습니다. 그런데 광고 컨테이너에 width: 90vw를 입력한 순간, 몇 주 동안 붙잡고 있던 문제가 두 줄로 해결됐습니다. 뷰포트 단위(Viewport Unit)가 단순히 "화면 크기에 맞추는 단위" 정도로 알려져 있지만, 실제로는 광고 렌더링 품질과 모바일 사용자 경험을 직접 좌우하는 핵심 CSS 속성입니다.
vw로 광고 컨테이너 문제 해결하기
width: 728px로 고정값을 주던 시절이 있었습니다. 데스크탑에서는 멀쩡해 보였는데, 700px 이하 화면에서 광고가 오른쪽으로 삐져나오거나 가로 스크롤이 생겼습니다. max-width를 추가해봤지만 부모 컨테이너를 벗어나는 경우는 막지 못했습니다. 솔직히 이건 예상 밖이었습니다. max-width가 만능인 줄 알았거든요.
그때 시도한 게 vw(Viewport Width) 단위였습니다. vw란 현재 브라우저 뷰포트 너비의 1%를 의미하는 단위로, 화면 크기가 달라져도 항상 그 비율로 요소 크기가 결정됩니다. width: 90vw로 바꾼 결과는 즉각적이었습니다. 360px짜리 저가형 안드로이드폰에서도, 1920px 모니터에서도 광고 영역이 화면 너비의 90%를 유지하며 딱 들어왔습니다.
여기서 max-width: 728px을 함께 쓰는 것이 핵심입니다. 이 조합이 의미하는 바는 명확합니다. 데스크탑처럼 넓은 화면에서는 728px을 넘지 않아 광고가 과도하게 커지지 않고, 모바일에서는 90vw가 728px보다 작으므로 화면에 맞게 자동으로 줄어듭니다. 미디어 쿼리 한 줄 없이 이게 가능하다는 점이 처음엔 신기하게 느껴졌습니다.
calc() 함수와의 조합도 실무에서 자주 씁니다. width: calc(100vw - 32px)는 좌우 각 16px 여백을 고정으로 확보하면서 나머지 공간을 광고가 채우는 설정입니다. 패딩이 없는 풀블리드(Full-bleed) 레이아웃, 즉 콘텐츠가 화면 끝까지 꽉 차게 펼쳐지는 구조에서 광고가 화면 경계에 바짝 붙어 보이는 문제를 깔끔하게 해결해줬습니다. 여기에 min-width: 300px을 추가하니 초소형 화면에서 광고가 아예 사라지던 현상도 없어졌습니다.
한 가지 주의할 부분이 있습니다. vw 단위는 스크롤바 너비를 포함해서 계산하는 경우가 있습니다. 페이지에 세로 스크롤바가 생기는 레이아웃에서 width: 100vw를 사용하면 스크롤바 너비(보통 15~17px)만큼 넘쳐서 가로 스크롤이 생기는 환경이 존재합니다. 이 엣지 케이스(Edge Case), 즉 일반적이지 않은 예외 상황을 모르고 100vw를 남발하다 보면 예상치 못한 레이아웃 깨짐을 마주하게 됩니다. 제 경험상 이건 실제로 한 번은 꼭 만나게 되는 함정입니다.
구글 애드센스 반응형 광고 가이드에서도 컨테이너 너비 설정이 광고 렌더링에 직접 영향을 준다는 점을 명시하고 있습니다(출처: Google AdSense 고객센터). data-full-width-responsive 속성을 활용한 자동 크기 조정 방식을 권장하는데, 이 속성이 제대로 작동하려면 컨테이너 너비가 정확히 잡혀 있어야 합니다. vw 단위 없이 고정값만 쓰면 이 자동 조정 기능이 의도대로 동작하지 않는 경우가 생깁니다.
핵심 설정을 정리하면 다음과 같습니다.
width: 90vw+max-width: 728px: 미디어 쿼리 없이 모바일·데스크탑 대응width: calc(100vw - 32px): 풀블리드 레이아웃에서 좌우 16px 여백 보장min-width: 300px: 초소형 화면에서 광고 미표시 현상 방지100vw주의: 세로 스크롤바 존재 시 가로 스크롤 발생 가능
vh 모바일 불안정 문제와 dvh 전환
vh(Viewport Height)는 vw와 달리 훨씬 까다로웠습니다. vh란 현재 뷰포트 높이의 1%를 의미하는 단위입니다. 이론상으로는 단순한데, 모바일 환경에서는 전혀 다른 이야기가 됩니다.
제가 직접 써봤는데, 모바일 사파리에서 sticky 광고 높이를 vh로 설정했더니 스크롤할 때마다 광고 영역 높이가 미세하게 흔들리는 현상이 나타났습니다. 처음엔 JavaScript 이벤트 버그인 줄 알고 몇 시간을 콘솔 로그만 뒤졌습니다. 결국 원인은 완전히 다른 곳에 있었습니다. iOS 사파리에서 vh는 주소창(URL 바)이 표시된 상태를 기준으로 계산되는데, 스크롤 중 주소창이 숨겨지면서 vh 기준값 자체가 재계산됩니다. 그 순간 레이아웃이 통째로 흔들리는 것입니다.
이 문제를 해결하기 위해 등장한 단위가 dvh(Dynamic Viewport Height)입니다. dvh란 브라우저 UI(주소창, 하단 탭바 등)의 표시 여부를 실시간으로 반영하여 뷰포트 높이를 동적으로 계산하는 단위입니다. 쉽게 말해, 주소창이 사라지면 그 빠진 높이만큼 dvh 값도 함께 커집니다. vh처럼 기준값이 고정되지 않아서 흔들림 현상이 원천적으로 사라집니다.
dvh와 함께 svh(Small Viewport Height)도 알아두면 좋습니다. svh란 브라우저 UI가 최대로 표시된 상태, 즉 주소창이 완전히 펼쳐져 있을 때의 뷰포트 높이를 고정값으로 쓰는 단위입니다. 항상 가장 작은 뷰포트 기준을 사용하기 때문에 레이아웃이 절대 잘리지 않는 안전한 선택입니다. dvh가 동적 대응을 원할 때 쓴다면, svh는 안정성이 최우선일 때 선택하는 단위라고 보면 됩니다.
dvh와 svh의 브라우저 지원 현황을 확인해보면 크롬, 파이어폭스, 사파리 등 주요 모던 브라우저에서 이미 지원됩니다(출처: Can I Use). 일부 구형 브라우저 대응이 필요한 서비스라면 height: 100vh; height: 100dvh; 형태로 fallback을 함께 작성하는 방식이 현재 가장 실용적인 접근입니다.
vh의 불안정성을 설명하면서 dvh나 svh 전환을 안내하지 않는 글들이 많습니다. ~라는 의견도 있지만, 실제로 써보니 vh 문제를 인식한 이상 dvh로 넘어가는 게 훨씬 합리적입니다. 저는 vh를 이 경험 이후로 광고 높이에 직접 쓰는 대신 max-height의 제한값 정도로만 활용하고, 실질적인 높이 제어는 dvh로 전환했습니다.
vw/vh 단위가 반응형 설계를 단순화한다는 점은 분명히 맞습니다. 다만 vh를 아무 생각 없이 모바일에 쓰면 사파리에서 반드시 한 번은 이 흔들림을 만나게 됩니다. 처음부터 dvh와 svh를 인지하고 시작하느냐 아니냐가 나중에 디버깅 시간을 몇 시간씩 아끼는 차이가 됩니다.
뷰포트 단위는 "있으면 편한 기능"이 아니라 모바일 레이아웃을 제대로 다루려면 반드시 이해해야 하는 기초입니다. vw는 광고 컨테이너와 calc() 조합으로 미디어 쿼리 없이 반응형을 완성하고, vh 대신 dvh를 기본으로 선택하는 습관을 들이는 것이 지금 시점에서 가장 실용적인 방향입니다. 아직 vh를 그대로 쓰고 계신다면, 한 번쯤 dvh로 바꿔보시는 것을 권해드립니다. 변화가 생각보다 즉각적으로 느껴질 겁니다.
참고:
MDN Web Docs – Viewport units: https://developer.mozilla.org/en-US/docs/Web/CSS/Viewport_units MDN Web Docs – clamp(): https://developer.mozilla.org/en-US/docs/Web/CSS/clamp Can I Use – CSS clamp: https://caniuse.com/css-math-functions Google AdSense 반응형 광고 가이드: https://support.google.com/adsense/answer/3543893 CSS Tricks – A Complete Guide to Viewport Units: https://css-tricks.com/fun-viewport-units