Когда 100vh обманывает: решение проблем с viewport на мобильных устройствах
Вы создаёте hero-секцию на всю высоту экрана. Она выглядит идеально в Chrome DevTools. Открываете её на телефоне — и нижняя часть контента обрезана. CTA-кнопка, над позиционированием которой вы работали, скрывается за адресной строкой браузера. Знакомо?
Это больше не особенность только Safari. Это следствие того, как фундаментально определены единицы viewport, и это затрагивает Chrome, Firefox и Samsung Internet на мобильных устройствах тоже.
Ключевые выводы
100vhна мобильных устройствах рассчитывается относительно большого viewport (с убранными панелями инструментов), который выше того, что пользователи видят при первой загрузке.- Используйте
100svhдля стабильных полноэкранных макетов, которые помещаются на видимом экране при отображении панелей инструментов. - Оставьте
100dvhдля случаев, когда вы явно хотите, чтобы макет изменял размер при изменении UI браузера, и готовы принять издержки перерисовки. - Комбинируйте единицы viewport с
env(safe-area-inset-bottom)на edge-to-edge дисплеях, чтобы контент не попадал за системный UI.
Почему 100vh обманывает на мобильных устройствах
Основная причина сводится к двум разным концепциям viewport:
- Layout viewport (макетный viewport): то, что браузер использует для расчёта CSS-длин, включая
vh - Visual viewport (визуальный viewport): то, что пользователь фактически видит на экране
Мобильные браузеры намеренно сохраняют layout viewport фиксированным, даже когда адресная строка и панель навигации убираются при прокрутке. Это означает, что 100vh рассчитывается относительно состояния с развёрнутыми панелями инструментов — большого viewport — и это значение больше, чем видимый экран при первой загрузке страницы.
Это не баг. Спецификация CSS определяет
vhкак эквивалентlvh(large viewport height). Браузер ведёт себя корректно. Ваш макет просто не учитывает это.
Вот почему мобильная эмуляция в DevTools выглядит нормально: она не симулирует динамический UI браузера. Адресная строка никогда не убирается в симуляторе.
Реальное влияние
- Hero-секции выходят за границы на 56–80px при первой загрузке
- Фиксированные футеры скрываются панелью навигации
- Полноэкранные модальные окна обрезают контент снизу
- Макеты «прыгают», когда панели инструментов убираются во время прокрутки (если вы используете
dvh)
Discover how at OpenReplay.com.
Современные решения: svh, dvh и lvh
CSS теперь предоставляет три явные единицы высоты viewport, которые соответствуют различным состояниям панелей инструментов:
| Единица | Представляет | Лучше всего для |
|---|---|---|
lvh | Большой viewport (панели убраны) | То же поведение, что у текущего vh |
svh | Малый viewport (панели видимы) | Стабильные макеты, всегда видимый контент |
dvh | Динамический viewport (текущее состояние) | Адаптивные макеты, но вызывает перерисовку |
Для большинства полноэкранных секций: используйте svh
.hero {
height: 100vh; /* Fallback для старых браузеров */
height: 100svh; /* Помещается в видимый экран при загрузке */
}
100svh задаёт размер элемента по наименьшему возможному viewport — это означает, что он помещается даже когда панели инструментов полностью видимы. Никакого переполнения, никакого обрезания.
Когда нужна динамическая подстройка: используйте dvh осторожно
.full-height {
height: 100vh; /* Fallback */
height: 100dvh; /* Изменяет размер при убирании панелей */
}
Предупреждение: dvh пересчитывается при изменении UI браузера во время прокрутки. Это может вызвать видимые сдвиги макета и перерисовку. Это не правильный выбор по умолчанию для большинства hero-секций или модальных окон — используйте его только когда вы явно хотите, чтобы элемент изменял размер вместе с состоянием панели инструментов.
Поддержка браузерами
svh, dvh и lvh поддерживаются в Chrome 108+, Safari 15.4+ и Firefox 101+. В совокупности это покрывает более 90% пользователей по всему миру. Для старых браузеров fallback 100vh покрывает остальное.
Граничные случаи, о которых стоит знать
Safe area insets: На edge-to-edge дисплеях (вырез/Dynamic Island на iPhone) комбинируйте единицы viewport с env(safe-area-inset-bottom), чтобы избежать попадания контента за системный UI:
.hero {
height: 100svh;
padding-bottom: env(safe-area-inset-bottom);
}
Экранная клавиатура: Виртуальная клавиатура в первую очередь влияет на visual viewport, и её взаимодействие с размерами layout viewport может различаться в разных браузерах и WebView. Это отдельно от единиц viewport, управляемых панелями инструментов, и может требовать обработки через JavaScript или VirtualKeyboard API как прогрессивное улучшение.
WebView и встроенные браузеры: Внутриприложенческие браузеры (Instagram, LinkedIn) часто имеют нестандартное поведение viewport. Тестируйте на реальных устройствах, а не только в системных браузерах.
Заключение
Перестаньте использовать 100vh по умолчанию для полноэкранных мобильных макетов. Используйте 100svh для стабильных секций, которые должны помещаться на видимом экране при загрузке. Оставьте 100dvh для случаев, когда динамическое изменение размера является намеренным. Новое семейство единиц viewport существует именно потому, что старое поведение всегда было компромиссом — теперь у вас есть инструменты, чтобы явно указать, что вы на самом деле хотите.
Часто задаваемые вопросы
Не везде. 100svh даёт вам наименьшую высоту viewport, которая короче, чем 100vh. Для элементов, которые должны заполнять экран только после убирания панелей инструментов, более подходящими могут быть 100lvh или 100dvh. Используйте 100svh специально для контента, который должен быть полностью видимым при первой загрузке страницы с отображаемыми панелями инструментов.
Может вредить. Поскольку dvh пересчитывается каждый раз, когда панель инструментов браузера убирается или появляется во время прокрутки, это вызывает пересчёт макета и перерисовку. Для статичных hero-секций или модальных окон эти издержки не нужны. Оставьте dvh для элементов, где вы намеренно хотите, чтобы высота отслеживала текущий видимый viewport в реальном времени.
Поведение различается. В стандартных iframe единицы viewport ссылаются на собственный viewport iframe, а не на родительскую страницу. В WebView, используемых внутриприложенческими браузерами вроде Instagram или Facebook, поведение панелей инструментов нестандартно, и расчёты единиц viewport могут быть непредсказуемыми. Всегда тестируйте на реальных устройствах в конкретных встроенных контекстах, с которыми сталкиваются ваши пользователи.
Единицы svh, dvh и lvh не затрагиваются виртуальной клавиатурой. Клавиатура уменьшает visual viewport, но не layout viewport, на который ссылаются эти единицы. Для обработки сдвигов макета, связанных с клавиатурой, обратите внимание на VirtualKeyboard API, который даёт вам программный контроль над тем, как ваш макет реагирует на изменения видимости клавиатуры.
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..