Главная идея
Один и тот же сайт нужно показать на телефоне 360px шириной, на планшете, на ноутбуке и на ретина-мониторе. Чтобы это работало предсказуемо, нужно различать три вещи: viewport (сколько места есть для раскладки), CSS-пиксели против физических пикселей (что такое «пиксель» на самом деле) и breakpoint (где менять раскладку). Без этого «лишние» полосы прокрутки, размытые картинки и съехавшая вёрстка кажутся магией.
Viewport — это иллюминатор корабля. Страница — огромный океан за бортом. Вы видите океан не целиком, а через окошко-иллюминатор: его размер и есть viewport. На телефоне иллюминатор узкий, на мониторе — широкий, но сам океан (страница) один и тот же. vw/vh — это «доли иллюминатора»: 50vw = половина ширины окошка, независимо от того, насколько велик океан за ним.
DPR — это плотность точек при печати. Один и тот же логотип можно напечатать «крупными точками» (DPR 1) или «мелкими, плотными точками» (DPR 2–3, как фотопечать). Размер логотипа на бумаге одинаковый, но во втором случае точек вдвое-втрое больше на тот же сантиметр — и картинка выглядит чётче. Поэтому для retina исходник нужен в большем разрешении.
Viewport: одно устройство — разные окна
Viewport — это область, в которой браузер раскладывает страницу, измеряемая в CSS-пикселях. Именно к ней привязаны проценты, media queries и единицы vw/vh. Разные устройства дают разный viewport:
Различают layout viewport (область, в которой считается раскладка) и visual viewport (то, что реально видно при зумe или появлении экранной клавиатуры). В большинстве задач достаточно работать с layout viewport.
meta viewport — обязательная строка
Без тега <meta name="viewport"> мобильный браузер «притворяется» широким десктопом (обычно ~980 CSS-пикселей) и уменьшает всю страницу так, чтобы она влезла. Текст становится крошечным, а media queries срабатывают неверно. Строка width=device-width, initial-scale=1 говорит браузеру: «ширина viewport = ширине устройства, масштаб 1:1».
<meta name="viewport" content="width=device-width, initial-scale=1">
CSS-пиксели против физических пикселей (DPR)
CSS-пиксель — это логическая единица раскладки, а не физическая точка экрана. DPR (window.devicePixelRatio) показывает, сколько физических точек приходится на один CSS-пиксель. На обычном экране DPR = 1, на retina = 2 или 3.
Единицы vw и vh
1vw — это 1% ширины viewport, 1vh — 1% его высоты. Они считаются от размеров окна, а не от родительского блока (этим отличаются от %). width: 50vw — всегда половина ширины окна, как бы глубоко элемент ни был вложен.
.hero {
width: 50vw; /* половина ширины окна */
height: 30vh; /* 30% высоты окна */
}
Когда применять и частая ошибка
Breakpoint — это момент, когда конкретный блок перестаёт удобно помещаться: строка становится слишком длинной, карточки сжимаются, меню ломается. Ставьте breakpoint по содержимому, а не по моделям телефонов.
100vw на блоке внутри страницы со скроллбаром даёт горизонтальную прокрутку — vw включает ширину вертикального скроллбара, а доступная для контента область уже. Чаще нужен width: 100%.window.devicePixelRatio и window.innerWidth — увидишь DPR и ширину viewport своего экрана прямо сейчас.