Почему 100vh выше экрана на телефоне — 3 причины и как починить

Ты сделал первый экран на height: 100vh. Заголовок, картинка, внизу кнопка «Начать». На компьютере — идеально. На телефоне кнопки нет. Она появляется, только если чуть прокрутить страницу.
И самое обидное: в режиме телефона в DevTools всё выглядит нормально. На странице web.dev об этом сказано прямо: в браузерах без выезжающих панелей, как Chrome на компьютере, все варианты высоты экрана совпадают. Баг живёт только на настоящем телефоне.
Вот три причины — от самой частой к редкой.
Причина 1. vh считается по «большому» экрану
У мобильного браузера две высоты экрана. Пока адресная строка видна — экран поменьше. Когда ты листаешь вниз и строка прячется — побольше.
vh берёт большую. Chrome на Android перешёл на это ещё в версии 56, в 2017 году, — чтобы вести себя так же, как Safari на iPhone. Команда Chrome так и написала: 100vh будет больше видимой высоты, пока адресная строка на экране.
Получается: при загрузке строка видна, а блок рассчитан так, будто её нет. Низ блока уходит за край.
Как проверить. Открой сайт на телефоне и чуть прокрути. Если кнопка выехала ровно в тот момент, когда спряталась адресная строка, — это она.
Как починить. С 2022 года во всех основных браузерах есть новые единицы (Chrome 108, Firefox 101, Safari 15.4):
svh— маленький экран, панели браузера видны;lvh— большой экран, панели спрятаны. Это старый добрыйvh;dvh— динамическая высота: меняется, когда панели выезжают и прячутся.
Для первого экрана бери svh:
.hero {
min-height: 100vh;
min-height: 100svh;
}
Первая строка — запасная для старых браузеров. Вторая её перекрывает там, где svh понимают. А min-height вместо height позволяет блоку вырасти, если текст не влез.
Почему не dvh? Он меняется вместе с панелями: пока человек листает, блок растёт и сжимается, а всё под ним сдвигается. К тому же web.dev предупреждает, что динамическая высота обновляется не на каждом кадре, а с задержкой. Оставь dvh для полноэкранных интерфейсов вроде чата, где высота должна совпадать точно.
В Tailwind класс h-screen — это как раз 100vh. Замени его на min-h-svh или h-dvh: эти классы есть с версии 3.4.
Причина 2. Клавиатура закрывает низ
Строка ввода чата закреплена внизу экрана. Нажимаешь на неё — выезжает клавиатура и закрывает поле.
web.dev объясняет: экранная клавиатура не считается панелью браузера и не меняет единицы высоты. А с Chrome 108 клавиатура на Android уменьшает только видимую область, но не макет страницы. Так же давно работает Safari на iPhone.
Как проверить. Тапни в поле внизу. Если оно остаётся под клавиатурой — причина здесь.
Как починить. В Chrome на Android можно попросить старое поведение одним ключом в meta-теге:
<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content">
Теперь при открытой клавиатуре макет сжимается, и нижняя панель встаёт над ней. Но команда Chrome, вводя этот ключ, предупреждала: работает он только в Chrome. На iPhone его не понимает даже Chrome — там под капотом движок Safari. Поэтому для iPhone высоту видимой области берут из JavaScript:
const vv = window.visualViewport;
function syncHeight() {
document.documentElement.style.setProperty('--app-height', `${vv.height}px`);
}
vv.addEventListener('resize', syncHeight);
syncHeight();
А в CSS контейнер чата получает height: var(--app-height, 100dvh).
Причина 3. Низ уходит под полоску жестов
Кнопка вроде на экране, но наполовину под полоской «домой» на iPhone или под навигационной полосой Android.
Так бывает, если в meta-теге стоит viewport-fit=cover. Страница растягивается до самых краёв, и браузер перестаёт отодвигать её от системных элементов. С версии 135 так умеет и Chrome на Android.
Как проверить. Найди в <meta name="viewport"> значение viewport-fit=cover. Нет его — эта причина не твоя.
Как починить. Добавь нижний отступ из безопасной зоны:
.bottom-bar {
padding-bottom: max(16px, env(safe-area-inset-bottom));
}
Подробно, что это за отступы и откуда они берутся, — в статье что такое safe area.
Если просишь ИИ сверстать мобильный экран, добавь в запрос одну строку: «не используй 100vh для высоты — бери 100svh с запасным 100vh, учти safe area». Первая и третья причины закрыты заранее. Базовая мобильная настройка — в статье как сделать сайт адаптивным.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.
Нужен ли ещё старый хак с --vh и innerHeight?
Для обычной высоты экрана — нет. Его придумали, когда единиц svh и dvh не было. Сейчас хватает запасной строки с 100vh. JavaScript остаётся нужен только для клавиатуры на iPhone.
Почему у position: fixed с height: 100% такой проблемы нет?
Команда Chrome описывала это прямо: закреплённый элемент с высотой 100% всегда занимает ровно видимую высоту и меняется вместе с адресной строкой. Это удобно для меню и модальных окон. Но первый экран живёт в обычном потоке страницы — ему нужен svh.
svh или dvh — что ставить по умолчанию?
svh. Блок никогда не уйдёт под панели и не будет дёргаться при прокрутке. dvh — только там, где интерфейс обязан занимать ровно видимую высоту.
А если стили не применяются вообще?
Тогда дело не в vh. Проверь, доходит ли правило до элемента, по списку почему CSS не работает.





