Адаптивный дизайн: Mobile First для карточки и эскизы clamp() и min()

Сегодня мы поговорим о том, как сделать так, чтобы ваш сайт выглядел потрясающе на любом устройстве - от крошечного смартфона до огромного монитора. Адаптивный дизайн - это не просто модный тренд, это базовая необходимость. Если ваш сайт неудобно читать с телефона, вы теряете посетителей и, как следствие, деньги. Но не переживайте, я проведу вас по основным принципам и, что не менее важно, расскажу о типичных ошибках, которые совершают даже опытные дизайнеры.

Содержание

  1. Коротко: 5 ключевых принципов
  2. Mobile First: начинаем с малого
  3. Резиновые сетки: Flexbox, Grid и min()
  4. Текучая типографика: clamp()
  5. Медиа-запросы и контейнерные запросы
  6. Адаптивные изображения
  7. Тёмная тема
  8. Топ-5 ошибок
  9. Собираем всё вместе: карточка товара
  10. Заключение

Коротко: 5 ключевых принципов

  1. Mobile First - проектируем с мобильного экрана, а не с десктопа.
  2. Резиновые сетки - Flexbox и Grid вместо фиксированной ширины.
  3. Текучая типографика - clamp() и min() для плавного масштабирования шрифтов.
  4. Контейнерные запросы - @container для независимых компонентов.
  5. Адаптивные изображения - 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 пикселей, и точка? Это работало, пока все сидели за одинаковыми мониторами. Сейчас разрешений экранов сотни, и фиксированная ширина - верный способ испортить впечатление. Вместо неё используем резиновые сетки: элементы занимают не жёстко заданные пиксели, а процент от ширины родителя или окна браузера.

Передвигайте ползунок, чтобы увидеть адаптацию сетки

Карточка 1
Карточка 2
Карточка 3

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

Аватар

Иван Петров

Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.

Компонент выглядит естественно на светлом фоне, все цвета хорошо читаются.

🌙 Тёмная тема

Фон: #121212, текст: #e0e0e0

Аватар

Иван Петров

Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.

Приглушённая палитра не бьёт по глазам, контраст остаётся комфортным.

@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;
      }
    }

Что здесь происходит:

  1. Mobile First: базовая раскладка в одну колонку - картинка сверху, текст снизу. Всё просто и логично.
  2. @container: когда контейнеру хватает 450px - карточка перестраивается в две колонки. При 650px - картинка занимает треть, текст две трети. Карточка сама решает, как выглядеть, глядя на родителя, а не на окно браузера.
  3. clamp(): заголовок, описание и цена плавно масштабируются без единого медиа-запроса для шрифтов.
  4. Touch-friendly: кнопка не меньше 44x44px - палец не промахнётся.
  5. Адаптивные изображения: <picture> подменяет горизонтальный баннер на вертикальный, WebP для сжатия, lazy loading для скорости.
  6. Тёмная тема: 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>, не забудьте про тёмную тему.

Избегайте фиксированных высот, проверяйте сайт на горизонтальный скролл, делайте кнопки удобными для пальца и не плодите лишние точки перелома. Тогда ваш сайт будет радовать глаз на любом устройстве, а пользователи скажут вам спасибо. И конверсия тоже.

Удачи в вёрстке! Пусть ваши интерфейсы будут гибкими и дружелюбными.