Сегодня мы поговорим о том, как сделать так, чтобы ваш сайт выглядел потрясающе на любом устройстве - от крошечного смартфона до огромного монитора. Адаптивный дизайн - это не просто модный тренд, это базовая необходимость. Если ваш сайт неудобно читать с телефона, вы теряете посетителей и, как следствие, деньги. Но не переживайте, я проведу вас по основным принципам и, что не менее важно, расскажу о типичных ошибках, которые совершают даже опытные дизайнеры.
Содержание
- Коротко: 5 ключевых принципов
- Mobile First: начинаем с малого
- Резиновые сетки: Flexbox, Grid и min()
- Текучая типографика: clamp()
- Медиа-запросы и контейнерные запросы
- Адаптивные изображения
- Тёмная тема
- Топ-5 ошибок
- Собираем всё вместе: карточка товара
- Заключение
Коротко: 5 ключевых принципов
- Mobile First - проектируем с мобильного экрана, а не с десктопа.
- Резиновые сетки - Flexbox и Grid вместо фиксированной ширины.
- Текучая типографика - clamp() и min() для плавного масштабирования шрифтов.
- Контейнерные запросы - @container для независимых компонентов.
- Адаптивные изображения - srcset, picture и современные форматы.
Mobile First: начинаем с малого
Раньше дизайнеры рисовали десктопную версию, а потом мучительно втискивали её в мобильный экран. Это всё равно что сшить пальто, а потом пытаться переделать его в футболку - трудно и результат так себе.
Принцип Mobile First переворачивает эту логику с ног на голову: сначала верстаем для самого маленького экрана (320-375px), а затем расширяем дизайн через медиа-запросы с min-width. На маленьком экране всё в одну колонку, навигация простая и понятная. Когда места становится больше - добавляем колонки, увеличиваем шрифты, вводим дополнительные элементы. Такой подход заставляет сфокусироваться на самом важном и не тащить в мобильную версию лишний груз.
С чего начинается любой адаптивный сайт? С viewport meta-тега. Без него браузер на телефоне думает, что ширина страницы - 980px, и просто ужимает её до размеров экрана. Пользователь видит микроскопический текст и вынужден зумить. Добавьте эту волшебную строку в <head> вашего HTML:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
Она говорит браузеру: «Ширина страницы равна ширине устройства, масштаб - 100%». Без этой строки адаптивный дизайн не заработает, даже если весь CSS написан идеально.
Попробуйте сами: Создайте HTML-страницу с viewport meta-тегом и без него. Откройте обе версии на телефоне и сравните - разница впечатляет.
Резиновые сетки: Flexbox, Grid и min()
Помните времена, когда сайты делали фиксированной ширины - 960 пикселей, и точка? Это работало, пока все сидели за одинаковыми мониторами. Сейчас разрешений экранов сотни, и фиксированная ширина - верный способ испортить впечатление. Вместо неё используем резиновые сетки: элементы занимают не жёстко заданные пиксели, а процент от ширины родителя или окна браузера.
Передвигайте ползунок, чтобы увидеть адаптацию сетки
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
— колонки сами решают, сколько их будет, в зависимости от ширины
Flexbox - отличный старт, если вы только знакомитесь с резиновыми раскладками. Всего три простых свойства творят чудеса:
/* Родитель */
display: flex;
flex-wrap: wrap; /* элементы переносятся, если не помещаются */
/* Дочерний элемент */
flex: 1 1 300px; /* растягивается на доступное место, но не уже 300px */
CSS Grid - более мощный инструмент, когда нужно больше контроля. Например, три колонки одинаковой ширины:
/* Три колонки, каждая - одна доля доступного пространства */
grid-template-columns: repeat(3, 1fr);
Если экран станет уже, колонки послушно сузятся. А когда места станет совсем мало, мы добавим медиа-запрос и скажем: «Grid, будь добр, сделай две колонки, а потом и одну». Никакой магии - чистая логика.
Есть ещё один изящный инструмент - функция min(). Она позволяет задать ширину, которая растягивается, но знает свой предел:
/* Растягивайся на всю ширину, но никогда не будь шире 600px */
width: min(100%, 600px);
Удобно для секций и контейнеров: на мобильном занимают весь экран, на десктопе - элегантно ограничены.
Попробуйте сами: Создайте сетку из трёх карточек на Grid. Добавьте медиа-запросы: при ширине экрана меньше 768px - две колонки, меньше 480px - одна. Поиграйтесь с границами и посмотрите, как сетка перестраивается.
Текучая типографика: буквы, которые дышат
Согласитесь, читать заголовок размером 48px на телефоне - то ещё удовольствие. Он либо не влезает, либо занимает пол-экрана. Раньше мы меняли размер шрифта в медиа-запросах: для телефона - 24px, для планшета - 32px, для десктопа - 48px. Это работает, но выглядит дёргано: при изменении ширины окна шрифт скачет рывками.
На помощь приходит функция clamp(). Звучит как что-то из арсенала плотника, но на деле - простое и элегантное решение. Она задаёт три значения: минимальное, предпочтительное и максимальное:
/* Минимум 1.5rem (~24px), максимум 3rem (~48px), а между ними - 4% от ширины окна */
font-size: clamp(1.5rem, 4vw, 3rem);
Что это значит на практике? Шрифт никогда не будет мельче 24px, никогда не вырастет больше 48px, а в промежутке - плавно подстраивается под ширину экрана. Никаких резких скачков, только плавное, «дышащее» масштабирование. Используйте clamp() для заголовков, подзаголовков и даже для основного текста. Только не переусердствуйте с нижней границей - текст всегда должен оставаться читаемым.
Удивительно гибкий заголовок
Этот текст тоже плавно меняет размер вместе с шириной контейнера.
font-size: clamp(1.5rem, 4vw, 3rem);
— минимум 1.5rem, максимум 3rem, а между ними плавно
Попробуйте сами: Возьмите любой заголовок с фиксированным font-size и замените его на clamp(). Изменяйте ширину окна и наблюдайте, как шрифт плавно подстраивается - это залипательно.
Медиа-запросы и контейнерные запросы
Многие думают, что с появлением Flexbox и Grid медиа-запросы отправились на пенсию. Это не так - они просто эволюционировали. Классический медиа-запрос смотрит на ширину окна браузера и до сих пор отлично справляется:
/* При ширине экрана от 768px показываем сайдбар */
@media (min-width: 768px) {
.sidebar {
display: block;
}
}
Но у классики есть слепое пятно: она не видит контекст. Представьте: один и тот же блок «Подвал сайта» живёт на главной странице внутри широкого контейнера, а на странице товара - внутри узкой боковой колонки. Медиа-запрос по ширине окна не различает эти ситуации. Он думает: «Окно широкое - значит, и подвал должен быть широким», хотя в сайдбаре ему отчаянно тесно.
И тут на сцену выходят контейнерные запросы (@container). Они смотрят не на всё окно, а на родительский контейнер элемента:
/* Родитель - отслеживаем его ширину */
.card-wrapper {
container-type: inline-size;
}
/* Дочерний элемент подстраивается под родителя */
@container (min-width: 400px) {
.card {
grid-template-columns: 1fr 1fr; /* две колонки, когда места хватает */
}
}
Пример «до и после»: одна и та же карточка товара используется в двух местах - в широкой сетке на главной и в узком сайдбаре. С обычным медиа-запросом карточка в сайдбаре «думает», что экран широкий, и упрямо показывает две колонки, которые не помещаются. С @container карточка «видит» свой реальный контейнер и послушно перестраивается в одну колонку. Это и есть настоящее модульное мышление: компонент сам решает, как ему выглядеть, независимо от того, куда его поместили.
@container карточка сама «видит» ширину родителя и перестраивается — в отличие от медиа-запросов, которые смотрят только на окно браузера.Название товара
Краткое описание товара
2 990 ₽
← Широкий контейнер (2 колонки) | Узкий контейнер (1 колонка) →
Название товара
Краткое описание
2 990 ₽
Попробуйте сами: Создайте два контейнера разной ширины с одинаковыми карточками внутри. Настройте @container так, чтобы в узком контейнере карточка была в одну колонку, а в широком - в две.
Посмотреть живой пример на CodePen →
Адаптивные изображения: быстрые и уместные
Загружать на телефон фотографию в 3000 пикселей - это форменное безобразие. Она весит пару мегабайт, грузится вечность и пожирает мобильный трафик пользователя. К счастью, у нас есть несколько инструментов, чтобы это исправить.
srcset и sizes - разные разрешения для разных экранов. Браузер сам выберет подходящую картинку, вы только подскажите ему варианты:
<img src="/photo-small.jpg" srcset="/photo-small.jpg 480w,
/photo-medium.jpg 768w,
/photo-large.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="Описание">
На телефоне загрузится маленькая версия, на десктопе - большая. Браузер достаточно умён, чтобы не ошибиться.
<picture> - разное содержание для разных экранов. Иногда на мобильном нужна не просто уменьшенная копия, а совсем другая композиция: горизонтальный баннер на десктопе и вертикальный на телефоне. Тег <picture> создан именно для этого:
<picture>
<source media="(min-width: 768px)" srcset="/banner-horizontal.webp">
<source media="(max-width: 767px)" srcset="/banner-vertical.webp">
<img src="/banner-fallback.jpg" alt="Баннер">
</picture>
Современные форматы. Используйте WebP или AVIF вместо старого доброго JPEG. WebP сжимает изображения в 2 раза лучше JPEG при том же качестве. AVIF ещё на 20-30% эффективнее WebP, но поддерживается пока не всеми браузерами. Для сжатия рекомендую Squoosh - бесплатный инструмент, который работает прямо в браузере и поддерживает оба формата. Загрузили картинку, выбрали формат, скачали - готово.
И не забудьте про loading="lazy" для изображений ниже первого экрана - они загрузятся только тогда, когда пользователь до них доскроллит. Ваши посетители с медленным интернетом скажут вам спасибо.
Попробуйте сами: Найдите на своём сайте изображение в JPEG. Сконвертируйте его в WebP и AVIF через Squoosh, сравните размеры. Добавьте <picture> с обоими форматами - удивитесь, насколько быстрее стал грузиться сайт.
Тёмная тема: адаптивность к предпочтениям
prefers-color-scheme делает это автоматически. Ниже — одна и та же карточка в светлой и тёмной теме.☀️ Светлая тема
Фон: #ffffff, текст: #1a1a1a
Иван Петров
Компонент выглядит естественно на светлом фоне, все цвета хорошо читаются.
🌙 Тёмная тема
Фон: #121212, текст: #e0e0e0
Иван Петров
Приглушённая палитра не бьёт по глазам, контраст остаётся комфортным.
@media (prefers-color-scheme: dark) { ... }
— сайт автоматически подстраивается под системную тему пользователя
Адаптивный дизайн - это не только про размеры экрана. Пользователи ожидают, что сайт подстроится под системную цветовую схему устройства. Медиа-запрос prefers-color-scheme делает это играючи:
@media (prefers-color-scheme: dark) {
body {
background: #121212;
color: #e0e0e0;
}
}
Маленький совет: не инвертируйте белый в чёрный грубой силой. Подберите приглушённую тёмную палитру - глаза скажут спасибо. И обязательно проверьте контрастность текста: на тёмном фоне она должна быть не ниже 4.5:1 (быстро проверить можно через WebAIM Contrast Checker).
Попробуйте сами: Добавьте поддержку тёмной темы на свой сайт через prefers-color-scheme. Переключите систему в тёмный режим и проверьте, всё ли читаемо.
Топ-5 ошибок, которые убивают адаптивность
Теория - это прекрасно, но давайте посмотрим правде в глаза: все мы наступаем на одни и те же грабли. Вот пять самых коварных.
Ошибка 1: Фиксированная высота блоков
Задать блоку height: 400px; - верный способ получить вылезший наружу контент. Текст может быть длиннее, шрифт на телефоне - крупнее, и вот уже содержимое накладывается на соседние элементы. Как исправить: используйте min-height или вообще не задавайте высоту - пусть контент сам решает. Для видео и изображений используйте aspect-ratio:
/* Соотношение 16:9, высота подстраивается автоматически */
aspect-ratio: 16 / 9;
Ошибка 2: Кнопки для муравьёв
На десктопе мы кликаем точным курсором. На телефоне - тыкаем пальцем, который в разы больше. Если кнопка 10x10 пикселей, пользователь будет промахиваться, злиться и уходить. Как исправить: минимальный размер кликабельных элементов - 44x44px (официальная рекомендация Apple и Google). Добавьте щедрые отступы вокруг текста внутри кнопки и достаточное расстояние между ссылками.
Ошибка 3: Горизонтальная прокрутка-призрак
Вы открываете сайт на телефоне, а снизу появляется горизонтальный скроллбар. Вы пытаетесь сдвинуть страницу - а там пустота. Знакомо? Это значит, какой-то элемент (таблица, картинка, блок с длинной строкой) оказался шире экрана. Как исправить: найдите виновника и почините. Для таблиц используйте overflow-x: auto; на контейнере. Для длинных слов и URL - overflow-wrap: break-word;.
Ошибка 4: Зоопарк из точек перелома
Некоторые дизайнеры делают отдельный макет для iPhone 12, iPhone 13, iPad Mini, iPad Pro и так далее. Это путь к выгоранию и необъятной таблице стилей. Как исправить: не привязывайтесь к конкретным устройствам. Ориентируйтесь на диапазоны: смартфоны до 480px, планшеты 481-768px, небольшие десктопы 769-1024px, большие экраны от 1025px. Ставьте точки перелома там, где вёрстка начинает ломаться - обычно хватает 2-3 штук.
Ошибка 5: Тяжеленные изображения
Фотография на 2 мегабайта на мобильном - это преступление против пользовательского опыта. Как исправить: используйте srcset, <picture> и современные форматы WebP/AVIF. Подробности - в разделе про адаптивные изображения выше.
Попробуйте сами: Откройте свой сайт на телефоне и честно пройдитесь по всем пяти пунктам. Есть горизонтальная прокрутка? Кнопки удобно нажимать? Контент нигде не вылезает? Будьте безжалостны - пользователи тоже.
Собираем всё вместе: карточка товара
Хватит теории - давайте посмотрим, как все эти принципы работают в реальном коде. Вот адаптивная карточка товара, которая подстраивается под что угодно.
HTML:
<div class="card-wrapper">
<article class="product-card">
<picture>
<source media="(min-width: 600px)" srcset="/product-horizontal.webp">
<source media="(max-width: 599px)" srcset="/product-vertical.webp">
<img src="/product-fallback.jpg" alt="Товар" loading="lazy">
</picture>
<div class="product-info">
<h2 class="product-title">Название товара</h2>
<p class="product-desc">Описание товара, которое может быть длинным или коротким.</p>
<span class="product-price">2 990 ₽</span>
<button class="product-btn">В корзину</button>
</div>
</article>
</div>
CSS:
/* Родитель - отслеживаем его ширину для @container */
.card-wrapper {
container-type: inline-size;
}
/* Базовая стилизация - Mobile First: одна колонка */
.product-card {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
padding: 16px;
border: 1px solid #e0e0e0;
border-radius: 12px;
}
.product-card img {
width: 100%;
height: auto;
border-radius: 8px;
}
.product-info {
display: flex;
flex-direction: column;
gap: 8px;
}
.product-title {
font-size: clamp(1.1rem, 2.5vw, 1.5rem);
margin: 0;
}
.product-desc {
font-size: clamp(0.85rem, 1.5vw, 1rem);
line-height: 1.5;
color: #555;
}
.product-price {
font-size: clamp(1rem, 2vw, 1.25rem);
font-weight: 700;
}
.product-btn {
padding: 12px 24px;
background: #7c3aed;
color: #fff;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
min-height: 44px;
min-width: 44px;
}
/* Контейнер шире 450px - две колонки */
@container (min-width: 450px) {
.product-card {
grid-template-columns: 1fr 1fr;
align-items: center;
}
}
/* Контейнер шире 650px - картинка слева, текст справа */
@container (min-width: 650px) {
.product-card {
grid-template-columns: 1fr 2fr;
}
}
/* Тёмная тема - меняем фон и цвет текста */
@media (prefers-color-scheme: dark) {
.product-card {
background: #1e1e1e;
border-color: #333;
}
.product-desc {
color: #aaa;
}
}
Что здесь происходит:
- Mobile First: базовая раскладка в одну колонку - картинка сверху, текст снизу. Всё просто и логично.
- @container: когда контейнеру хватает 450px - карточка перестраивается в две колонки. При 650px - картинка занимает треть, текст две трети. Карточка сама решает, как выглядеть, глядя на родителя, а не на окно браузера.
- clamp(): заголовок, описание и цена плавно масштабируются без единого медиа-запроса для шрифтов.
- Touch-friendly: кнопка не меньше 44x44px - палец не промахнётся.
- Адаптивные изображения: <picture> подменяет горизонтальный баннер на вертикальный, WebP для сжатия, lazy loading для скорости.
- Тёмная тема: prefers-color-scheme мягко затемняет фон карточки.
📱 280–399px: 1 колонка | 📲 400–599px: 2 колонки | 🖥️ 600px+: картинка слева, текст справа
Открыть живой пример на CodePen →
Попробуйте сами: Скопируйте код и добавьте третью точку перелома в @container для очень широких контейнеров - например, больше 900px. Увеличьте отступы и добавьте лёгкую тень карточке.
Заключение
Адаптивный дизайн - это не магия и не чёрный пояс по CSS. Это просто набор понятных техник, которые работают вместе. Начните с Mobile First и viewport meta-тега, освойте резиновые сетки на Flexbox или Grid, подружитесь с clamp() и min() для типографики. Попробуйте @container - это будущее модульных компонентов. Адаптируйте изображения через srcset и <picture>, не забудьте про тёмную тему.
Избегайте фиксированных высот, проверяйте сайт на горизонтальный скролл, делайте кнопки удобными для пальца и не плодите лишние точки перелома. Тогда ваш сайт будет радовать глаз на любом устройстве, а пользователи скажут вам спасибо. И конверсия тоже.
Удачи в вёрстке! Пусть ваши интерфейсы будут гибкими и дружелюбными.