Viewport units

Viewport units (vw, vh, vmin, vmax, svh, lvh, dvh) — единицы измерения, привязанные к размеру видимой области браузера (viewport). Позволяют создавать элементы, которые масштабируются пропорционально экрану.

Зачем нужно

Процентные единицы (%) зависят от размера родителя. Viewport units зависят от размера окна браузера, что идеально для hero-секций, полноэкранных слайдов, адаптивной типографики и элементов, которые должны занимать определённую долю экрана.

Где используется

  • Hero-секции на полный экран
  • Адаптивная типографика (clamp с vw)
  • Полноэкранные модальные окна и оверлеи
  • Минимальная высота контентных областей
  • Адаптивные отступы

Предпосылки

Классические единицы

.element {
  /* 1vw = 1% ширины viewport */
  width: 50vw;           /* Половина ширины экрана */

  /* 1vh = 1% высоты viewport */
  height: 100vh;         /* Полная высота экрана */

  /* vmin = min(vw, vh) — меньшая сторона */
  font-size: 5vmin;      /* На портретном — от ширины, на ландшафтном — от высоты */

  /* vmax = max(vw, vh) — большая сторона */
  width: 80vmax;
}

Что такое viewport

┌─────────────────────────┐
│    Адресная строка       │
├─────────────────────────┤
│                         │
│      Viewport           │  ← vh, vw считаются от этой области
│                         │
│                         │
│                         │
├─────────────────────────┤
│    Панель инструментов   │
└─────────────────────────┘

Проблема 100vh на мобильных

На мобильных браузерах адресная строка скрывается при скролле, изменяя высоту viewport. 100vh всегда равен максимальной высоте (с убранной адресной строкой), поэтому при первом показе страницы элемент оказывается выше видимой области.

/* ПРОБЛЕМА — на мобильном 100vh больше видимой области */
.hero {
  height: 100vh; /* Часть контента обрезается */
}

Новые единицы (Small, Large, Dynamic)

svh / svw — Small Viewport

/* svh = высота viewport МИНУС адресная строка и панели */
/* Гарантированно помещается на экране */
.hero {
  min-height: 100svh;
}

lvh / lvw — Large Viewport

/* lvh = высота viewport БЕЗ адресной строки (максимальная) */
/* = старый 100vh */
.hero {
  min-height: 100lvh;
}

dvh / dvw — Dynamic Viewport

/* dvh = текущая высота viewport (меняется при скролле) */
/* Самый точный, но может вызывать reflow */
.hero {
  min-height: 100dvh;
}

Сравнение

Адресная строка показана:
┌──────────────────────┐
│ █████ Адресная строка │
├──────────────────────┤  ← svh начинается здесь
│                      │
│   svh = dvh          │  ← dvh = svh (в данный момент)
│                      │
│                      │
│   lvh > dvh          │  ← lvh всегда полная высота
│                      │
└──────────────────────┘

Адресная строка скрыта (после скролла):
┌──────────────────────┐  ← lvh начинается здесь
│                      │
│   dvh = lvh          │  ← dvh теперь равен lvh
│                      │
│   svh < dvh          │
│                      │
│                      │
└──────────────────────┘

Рекомендации

/* Для hero-секций — dvh (лучший вариант) */
.hero {
  min-height: 100dvh;
}

/* Fallback для старых браузеров */
.hero {
  min-height: 100vh;  /* Fallback */
  min-height: 100dvh; /* Браузеры, поддерживающие dvh, применят это */
}

/* Для фиксированных оверлеев — svh (точно поместится) */
.modal-overlay {
  height: 100svh;
}

Практические примеры

Hero-секция

.hero {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 5vw;
}

.hero-title {
  font-size: clamp(2rem, 1rem + 4vw, 5rem);
}

Полноэкранный слайд

.slide {
  width: 100vw;
  height: 100dvh;
  scroll-snap-align: start;
}

Адаптивная типографика

h1 {
  /* vw для плавного масштабирования, clamp для ограничения */
  font-size: clamp(1.5rem, 1rem + 3vw, 4rem);
}

/* Только vw — ОПАСНО (может быть слишком мелким или крупным) */
h1 {
  font-size: 5vw; /* На 320px = 16px, на 1920px = 96px — слишком! */
}

Адаптивные отступы

section {
  padding-block: clamp(2rem, 1rem + 5vh, 6rem);
  padding-inline: clamp(1rem, 2vw, 4rem);
}

Модальное окно

.modal {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100svh; /* svh — гарантированно помещается */
  display: grid;
  place-items: center;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;
}

.modal-content {
  width: min(90vw, 600px);
  max-height: 80svh;
  overflow-y: auto;
  padding: 32px;
  background: white;
  border-radius: 12px;
}

vmin и vmax

/* vmin — адаптация к меньшей стороне */
.square {
  width: 50vmin;
  height: 50vmin;
  /* Всегда квадрат, всегда помещается */
}

/* vmax — адаптация к большей стороне */
.overlay-text {
  font-size: 10vmax;
  /* Всегда крупный, независимо от ориентации */
}

Частые ошибки

  1. 100vh на мобильных — используйте 100dvh или 100svh:
    /* ПРОБЛЕМА */
    .hero { height: 100vh; }
    /* РЕШЕНИЕ */
    .hero { min-height: 100dvh; }
    
  2. font-size только через vw — на маленьких экранах текст слишком мелкий:
    /* ОШИБКА */
    body { font-size: 2vw; }
    /* ПРАВИЛЬНО */
    body { font-size: clamp(1rem, 0.5rem + 1vw, 1.25rem); }
    
  3. width: 100vw вызывает горизонтальный скролл — 100vw включает scrollbar:
    /* ОШИБКА — с вертикальным скроллбаром появится горизонтальный */
    .section { width: 100vw; }
    /* ПРАВИЛЬНО */
    .section { width: 100%; }
    
  4. Забыли fallback для dvh/svh — старые браузеры не поддерживают

Практика

  • Создать hero-секцию на 100dvh с fallback на 100vh
  • Сделать адаптивную типографику через clamp + vw
  • Создать модальное окно с svh и min(90vw, 600px)
  • Сравнить 100vh, 100svh, 100lvh, 100dvh на мобильном
  • Создать квадратный элемент через vmin

Связанные темы

Ресурсы