Адаптивность: один сайт для всех экранов
Мета-viewport, подход mobile-first, медиазапросы по ширине и по предпочтениям человека, clamp() и container queries.
Что ты научишься делать
- Сделаешь так, чтобы сайт не был мелким на телефоне
- Напишешь медиазапросы, которые добавляют, а не чинят
- Уберёшь половину медиазапросов за счёт гибких значений
#Первая строка адаптивности
Без специальной инструкции мобильный браузер притворяется, что экран шириной около 980 пикселей, и уменьшает страницу целиком. Сайт «помещается», но текст становится нечитаемым.
Одна строка в head это выключает.
<meta name="viewport" content="width=device-width, initial-scale=1">width=device-width означает «считай ширину экрана настоящей», initial-scale=1 — «не масштабируй при загрузке». Мы писали эту строку ещё во втором уроке, теперь понятно зачем.
#Mobile-first: сначала узкий экран
Есть два подхода. Можно написать стили для широкого экрана, а потом медиазапросами всё «чинить» для узкого. А можно наоборот: написать базовые стили для телефона, а затем добавлять для больших экранов.
Второй путь удобнее. На телефоне раскладка обычно простая — одна колонка. Значит, базовые стили получаются короткими, а медиазапросы только усложняют картину по мере роста экрана.
Так не надо
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 900px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 600px) {
.grid { grid-template-columns: 1fr; }
}Сначала сложное, потом два запроса на упрощение. Каждое новое требование добавляет ещё одно исключение.
Так надо
.grid {
display: grid;
gap: 16px;
}
@media (min-width: 600px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 900px) {
.grid { grid-template-columns: repeat(4, 1fr); }
}База — одна колонка, дальше только добавление. Читается сверху вниз как рассказ о том, как растёт экран.
#Медиазапросы
/* Экран шире 768 пикселей */@media (min-width: 768px) { .sidebar { display: block; }}/* Диапазон */@media (min-width: 768px) and (max-width: 1199px) { .card { font-size: 1rem; }}/* Только для печати */@media print { .site-header { display: none; }}#Медиазапросы о человеке, а не об экране
Ширина — не единственное, о чём можно спросить. Браузер знает и о настройках человека.
/* Человек выбрал тёмную тему в системе */@media (prefers-color-scheme: dark) { :root { --bg: #10141f; --text: #e8eaf2; }}/* Человеку мешает движение: вестибулярные нарушения, укачивание */@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }}/* Устройство с грубым указателем — палец, а не мышь */@media (pointer: coarse) { .btn { min-height: 44px; }}#Половина медиазапросов не нужна
Современный CSS умеет подстраиваться без всяких запросов. Три приёма закрывают большинство случаев.
/* 1. Сетка сама считает число колонок */.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem;}/* 2. Размер плавно растёт вместе с окном */h1 { font-size: clamp(1.8rem, 1.2rem + 3vw, 3.5rem);}/* 3. Контейнер сам себя ограничивает и центрирует */.container { width: min(100% - 2rem, 1200px); margin-inline: auto;}Функции min(), max() и clamp() считают значение прямо во время отрисовки. width: min(100% - 2rem, 1200px) читается так: «сколько поместится, но не больше 1200 пикселей, и всегда с отступом по 1rem с краёв».
#Картинки на разных экранах
Про srcset и picture мы говорили в пятом уроке. Но есть правило, без которого не обходится ни один проект.
img, video, svg { max-width: 100%; height: auto;}#Container queries: запрос к родителю
У медиазапросов есть слабое место: они знают только ширину окна. А карточка может стоять и в широкой колонке, и в узкой боковой панели на одном и том же экране.
Контейнерные запросы спрашивают про ширину родителя. Компонент становится по-настоящему самостоятельным: он подстраивается под место, куда его положили.
.card-wrap { container-type: inline-size;}/* Сработает, когда ширина .card-wrap больше 400px, независимо от размера окна */@container (min-width: 400px) { .card { display: grid; grid-template-columns: 120px 1fr; gap: 16px; }}Где спотыкаются почти все
Забыт мета-viewport
Почему Мобильный браузер уменьшает всю страницу, и текст становится нечитаемым. Медиазапросы при этом не срабатывают — браузер считает экран широким.
Как надо Строка <meta name="viewport" content="width=device-width, initial-scale=1"> в head.
Точки перелома подобраны под конкретные модели телефонов
Почему Устройств сотни, и список устаревает каждый год. Между «подогнанными» размерами вёрстка ломается.
Как надо Ставь границу там, где раскладка начинает выглядеть плохо при плавном изменении ширины.
Ширины блоков заданы в пикселях
Почему Блок шириной 400px на экране 360px вызывает горизонтальную прокрутку всей страницы.
Как надо Бери max-width, проценты, fr и min().
overflow-x: hidden на body «чтобы не скроллилось вбок»
Почему Это прячет симптом. Элемент по-прежнему вылезает, просто теперь его не видно, а вместе с ним ломается position: sticky.
Как надо Найди, что именно выходит за край, и почини причину.
- Адаптивность responsive design
- Свойство сайта подстраиваться под размер экрана без отдельной мобильной версии.
- Медиазапрос media query
- Условие, при котором применяются стили: ширина, тема, тип указателя.
- Точка перелома breakpoint
- Ширина, на которой меняется раскладка.
- Mobile-first mobile first
- Подход, при котором базовые стили пишутся для узкого экрана, а запросы только добавляют.
- Контейнерный запрос container query
- Условие по ширине родителя, а не окна.
Проверь себя
Браузер притворится, что экран шириной около 980px, и отмасштабирует всё. Медиазапросы при этом тоже не сработают как надо.
Один и тот же сайт, но порядок написания стилей другой: от простого к сложному, через <code>min-width</code>.
Функция <code>min()</code> выбирает меньшее из значений. На узком экране это ширина минус отступы, на широком — 1200px.
Благодаря этому компонент подстраивается под место, куда его положили: в широкой колонке он один, в узкой панели — другой.
Верных вариантов несколько.
Ширина — про экран. Тема, чувствительность к движению и тип указателя — про человека и его настройки.
Задание
Сделать раскладку адаптивной
Переделай раскладку из прошлого урока так, чтобы она работала на всех экранах:
- базовые стили — одна колонка, блоки идут друг под другом (это состояние для телефона);
- начиная с 700px — двухколоночная схема с меню слева;
- заголовок задай через
clamp(), без медиазапросов; - контейнер:
width: min(...), чтобы не прилипал к краям; - картинкам добавь
max-width: 100%.
Подсказка 1
База — просто gap и одна колонка. Ничего указывать не нужно: грид по умолчанию кладёт всё в одну колонку.
Подсказка 2
Дальше добавь: @media (min-width: 700px) { .layout { grid-template-columns: 160px 1fr; grid-template-areas: "header header" "nav main" "footer footer"; } }
Подсказка 3
Не забудь назначить областям блоки внутри того же медиазапроса.
Показать решение
<div class="layout"> <header class="layout__header"><h1>Кофейня «Полка»</h1></header> <nav class="layout__nav">Меню</nav> <main class="layout__main">Содержимое страницы</main> <footer class="layout__footer">Подвал</footer></div>* { box-sizing: border-box; }img, video, svg { max-width: 100%; height: auto;}.layout { display: grid; gap: 12px; width: min(100% - 1.5rem, 900px); margin-inline: auto;}.layout > * { padding: 14px; border-radius: 10px;}.layout__header { background: #6544e0; color: #fff; }.layout__nav { background: #2b9c74; color: #fff; }.layout__main { background: #f4f4f8; }.layout__footer { background: #14173a; color: #fff; }h1 { margin: 0; font-size: clamp(1.3rem, 1rem + 2.5vw, 2.2rem); line-height: 1.15;}@media (min-width: 700px) { .layout { grid-template-columns: 160px 1fr; grid-template-areas: "header header" "nav main" "footer footer"; } .layout__header { grid-area: header; } .layout__nav { grid-area: nav; } .layout__main { grid-area: main; } .layout__footer { grid-area: footer; }}