Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем нюанс vh на мобильных устройствах?
vh (viewport height) на мобильных устройствах имеет критический баг — высота браузера меняется при появлении и исчезновении адресной строки и других элементов интерфейса. Это вызывает прыганье контента, что портит пользовательский опыт.
Проблема: динамическая высота viewport
На мобильных устройствах браузер имеет две разные высоты:
- Initial viewport height — высота браузера с адресной строкой видимой
- Dynamic viewport height — высота браузера, когда адресная строка скрыта при прокрутке
/* Проблемный код */
body {
height: 100vh; /* 100vh = текущая высота viewport */
}
/* На мобиле:
- При открытии страницы: 100vh = 667px (с адресной строкой)
- При прокрутке вверх: 100vh = 750px (без адресной строки)
- Результат: контент прыгает! */
Это происходит потому, что браузер пересчитывает 1vh при каждом изменении видимой части интерфейса.
Решение 1: использовать 100dvh (Dynamic Viewport Height)
dvh (dynamic viewport height) — новый стандарт, который всегда представляет фактическую видимую высоту:
/* ✅ Правильно — адресная строка не влияет */
body {
height: 100dvh;
overflow: hidden;
}
/* Других вариантов: 100svh (small), 100lvh (large) */
Другие единицы viewport:
/* dvh — dynamic viewport height (высота меняется при изменении адресной строки) */
height: 100dvh; /* рекомендуется */
/* svh — small viewport height (всегда считает адресную строку как видимую) */
height: 100svh; /* не меняется никогда */
/* lvh — large viewport height (всегда считает адресную строку как скрытую) */
height: 100lvh; /* фиксированная большая высота */
Решение 2: JavaScript переменные
Для браузеров, не поддерживающих dvh, используй JavaScript для расчета реальной высоты:
// Установи переменную CSS с реальной высотой
function setViewportHeight() {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty("--vh", `${vh}px`);
}
// Вызови при загрузке и при изменении размера
setViewportHeight();
window.addEventListener("resize", setViewportHeight);
window.addEventListener("orientationchange", setViewportHeight);
/* Используй переменную вместо vh */
body {
height: calc(var(--vh, 1vh) * 100);
overflow: hidden;
}
Решение 3: позиционирование вместо height
Для полноэкранных блоков используй позиционирование вместо height:
/* ❌ Может прыгать */
.full-screen {
height: 100vh;
}
/* ✅ Не прыгает */
.full-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%; /* или 100vh — тут не прыгает с fixed */
}
Практический пример: мобильное приложение
import { useEffect } from react;
function MobileLayout() {
useEffect(() => {
// Установь высоту при загрузке
const handleResize = () => {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty(--vh, `${vh}px`);
};
handleResize();
window.addEventListener(resize, handleResize);
window.addEventListener(orientationchange, handleResize);
return () => {
window.removeEventListener(resize, handleResize);
window.removeEventListener(orientationchange, handleResize);
};
}, []);
return (
<div className="min-h-dvh flex flex-col">
<header>Шапка</header>
<main className="flex-1 overflow-y-auto">Контент</main>
<footer>Подвал</footer>
</div>
);
}
Tailwind CSS решение
В Tailwind используй dvh или CSS переменную:
/* globals.css */
:root {
--vh: 1vh;
}
@supports (height: 1dvh) {
:root {
--vh: 1dvh; /* поддержка dvh */
}
}
<!-- Используй в классах -->
<div class="h-[calc(var(--vh)*100)]">Полная высота</div>
Сравнение подходов
| Подход | Плюсы | Минусы | Когда использовать |
|---|---|---|---|
| 100dvh | Современный стандарт, просто | Не поддерживается старыми браузерами | Новые проекты |
| JavaScript переменная | Работает везде | Требует JS код | Поддержка старых браузеров |
| position: fixed | Надежно | Не подходит для всех случаев | Полноэкранные компоненты |
| Поднятие viewport (viewport-fit) | Решает проблему notch | Не универсально | iPhone с notch |
Viewport-fit для iPhone с notch
Для устройств с вырезом (notch) используй meta тег:
<!-- В head -->
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
/* Учти safe area */
body {
padding-top: max(12px, env(safe-area-inset-top));
padding-bottom: max(12px, env(safe-area-inset-bottom));
padding-left: max(12px, env(safe-area-inset-left));
padding-right: max(12px, env(safe-area-inset-right));
}
Ключевые выводы
- 100vh на мобиле прыгает при скрытии/показе адресной строки
- 100dvh — новый стандарт, используй его (с fallback)
- JavaScript
window.innerHeight— надежный способ для старых браузеров - position: fixed — не прыгает, но не всегда подходит
- viewport-fit=cover — для безопасных зон на iPhone
- Всегда тестируй на реальных мобильных устройствах, не только в DevTools