Привычные viewport-единицы появились в CSS ещё в 2012 году. Конструкция 100vh для секции в полный экран — настолько распространённый паттерн, что его пишут не задумываясь. И всё бы хорошо, если бы не одна досадная мелочь: на мобильных браузерах 100vh работает не так, как ожидаешь. Контент режется снизу, центрирование съезжает, а полноэкранная модалка не показывает кнопку «закрыть».
В 2022 году CSS Working Group добавили новый набор единиц — svh, lvh и dvh (плюс их аналоги для ширины и логических осей). К 2024-му они стали Baseline-широкоподдержанными, и тянуть с переходом больше нет смысла. Разберёмся, в чём именно проблема старого 100vh, что значат три новые буквы перед vh, и какие задачи они закрывают на практике.
В чём проблема 100vh на мобильном
На десктопе размер окна браузера фиксирован: пользователь не открывает и не закрывает свой address bar по ходу скролла. На мобильном — ровно наоборот. iOS Safari и Chrome для Android прячут адресную строку и нижнюю панель навигации, когда пользователь начинает листать вниз: освобождают экран ради контента. Когда пользователь скроллит наверх — панели возвращаются.
Высота viewport в этих двух состояниях разная. Старая единица vh вычисляется один раз и фиксируется на большем из двух значений — том, что получится после скрытия панелей. То есть когда страница только открылась и панели ещё на экране, элемент с height: 100vh на самом деле выше видимой области, и низ контента уезжает за нижнюю панель.
Самый болезненный сценарий — полноэкранный загрузочный экран:
.loader {
position: fixed;
inset: 0 0 auto 0;
height: 100vh;
display: grid;
place-items: center;
}
Иконка лоадера якобы по центру по CSS, а визуально смещена вниз — потому что центр считается от высоты, которая больше реально видимой. Та же история со sticky-футером в модалке (его не видно), с CTA-кнопкой в полноэкранной hero-секции (она частично за нижней панелью), с любым layout, где центр или низ обязан быть в кадре.

Малый, большой и динамический viewport
Решение спецификации CSS Values 4 — разделить понятие viewport на три состояния, каждому соответствует свой префикс перед vh:
svh — small viewport
Высота viewport, когда browser-bars максимально развёрнуты. То есть самое маленькое значение из всех возможных. Полноэкранный элемент с height: 100svh гарантированно поместится в видимую область даже при показанной адресной строке — и при скролле не будет смещаться.
lvh — large viewport
Высота viewport, когда browser-bars максимально свёрнуты. Это в точности то, как ведёт себя классический vh: просто фиксированная большая высота. Самостоятельной пользы в верстке у lvh почти нет — он скорее нужен, чтобы было полное семейство и в спецификации не было исключений.

dvh — dynamic viewport
Динамическая высота: браузер пересчитывает её на лету в зависимости от текущего состояния панелей. Когда адресная строка свёрнута — dvh равен lvh; когда развёрнута — svh. По дороге между этими состояниями элемент «дышит» вместе с UI браузера.
Звучит как универсальное решение — и иногда так и есть, — но у динамического пересчёта есть нюансы, к которым вернёмся в отдельном разделе ниже.
Не только высота
Важная деталь, которую часто упускают в обзорах: новых единиц на самом деле не три, а целая сетка. Префиксы s, l, d применимы ко всем классическим viewport-единицам, не только к высоте.
| Тип | Small (s*) | Large (l*) | Dynamic (d*) |
|---|---|---|---|
| Высота | svh | lvh | dvh |
| Ширина | svw | lvw | dvw |
| Меньшая сторона | svmin | lvmin | dvmin |
| Бо́льшая сторона | svmax | lvmax | dvmax |
Дополнительно есть логические аналоги svi/lvi/dvi (inline-направление текста) и svb/lvb/dvb (block-направление). Они полезны при writing-mode: vertical-rl, поддержке RTL-вёрстки или вертикальной восточноазиатской типографики — там, где «высота» и «ширина» зависят от направления письма. Подробный разбор самого свойства writing-mode и того, когда оно нужно — в отдельной статье.
Где это реально пригождается
Загрузочный экран
Тот самый кейс из вступления. Замена vh на svh убирает съезжающее центрирование одной строкой:
.loader {
position: fixed;
inset: 0 0 auto 0;
height: 100svh;
display: grid;
place-items: center;
}
Модалка со sticky-хэдером и футером
Полноэкранная модалка, у которой шапка и подвал прибиты, а в середине скроллящийся контент. Если использовать 100vh, на мобильном Safari нижний футер уезжает за адресную строку, и пользователь не видит кнопку «Закрыть».
.modal {
position: fixed;
inset: 0;
height: 100svh;
display: grid;
grid-template-rows: auto 1fr auto;
}
.modal__header,
.modal__footer { position: sticky; }
.modal__body { overflow-y: auto; }
С 100svh футер гарантированно в кадре. Альтернатива — 100dvh, тогда модалка будет немного увеличиваться при скрытии адресной строки. Что выбрать — вопрос вкуса; в пользу svh — стабильное положение элементов без «дыхания».
Hero-секция за вычетом фиксированного хэдера
Классическая задача: главная секция должна занять всю оставшуюся после хэдера высоту экрана. Стандартное решение через calc() теперь использует новую единицу:
:root {
--header-height: 60px;
}
.site-header {
position: sticky;
top: 0;
min-height: var(--header-height);
}
.hero {
min-height: calc(100svh - var(--header-height));
}
На десктопе изменений не будет, на мобильном — пропадёт обрезка декора у нижнего края секции, который раньше прятался за нижней панелью браузера.
dvh как универсальный дефолт — не лучшая идея
Соблазн заменить все vh на dvh в проекте сразу понятный: пусть всё подстраивается. На практике у этой замены есть две проблемы.
Скачущая типографика. Если задать font-size через dvh, размер шрифта будет меняться по ходу скролла — в момент сворачивания/разворачивания адресной строки. Выглядит это как глитч:
/* так делать не надо */
h1 {
font-size: calc(1rem + 5dvh);
}
Для свойств, у которых пересчёт виден глазу — font-size, padding, gap, transform, — dvh противопоказан. Для них берём svh: значение зафиксируется по нижней границе и не будет дёргаться.
Производительность. Динамический пересчёт стилей при скролле — работа для движка. На простой странице это незаметно, но на тяжёлом layout с большим количеством dvh-зависимых свойств можно заметить просадку плавности скролла. Точные замеры зависят от страницы; общее правило — не использовать dvh там, где достаточно svh.
Фолбэк для очень старых браузеров
Несмотря на то, что новые единицы давно Baseline, всё ещё могут попадаться браузеры до 2022 года — чаще всего на старых Android-устройствах. Удобный паттерн фолбэка использует тот факт, что незнакомое CSS-значение просто игнорируется:
.hero {
height: 100vh; /* старый движок прочитает только это */
height: 100svh; /* новый движок переопределит */
}
Если хочется явной фича-детекции с разной логикой — есть @supports:
@supports (height: 100svh) {
.hero { height: 100svh; }
}
Но в большинстве проектов хватает простого каскадного подхода выше.
Поддержка браузерами
Итог
Краткое практическое правило для повседневной верстки:
- Полноэкранные секции, модалки, загрузочные экраны — берите 100svh. Это самый частый и самый безопасный выбор.
- Если по дизайну важно, чтобы блок «дышал» вместе с UI браузера — 100dvh. Но не для свойств, где скачки заметны.
- 100lvh на практике эквивалентен старому 100vh и в новой верстке не нужен.
- Для font-size, отступов и других визуальных свойств — только svh или совсем без viewport-единиц.
- Фолбэк для старых движков — парная декларация height: 100vh; height: 100svh;.
Реальную разницу между всеми тремя единицами лучше всего смотреть на телефоне — на десктопе address bar не сворачивается, и все три значения визуально совпадают. Если контейнерные запросы и адаптивный дизайн в целом всё ещё в стадии освоения — начните с обзорной статьи про CSS Container Queries: новые viewport-единицы и контейнерные запросы — две части одной общей истории про адаптацию вёрстки к реальному окну.
Комментарии (0)