Вёрстка адаптивов

Кодинг
Q_Time

8 минуты чтения

hero image

Содержание

В предыдущих статьях мы разобрали теорию адаптивности и обсудили, как готовить макеты в Figma. Теперь переходим к коду. Но прежде чем говорить о конкретных подходах, нужно разобраться с инструментом, на котором строится большая часть адаптивной вёрстки — медиа-запросами.

Медиа-запросы @media

Медиа-запрос — это условие в CSS, которое проверяет характеристики устройства и применяет стили только в том случае, если условие выполняется. Синтаксис выглядит так:

Я красный на десктопе и зелёный на мобильном. Сужайте окно до 768px.

<div class="box">Текст...</div>
      

.box {
  background: #FE3904;
  color: white;
}
 
@media (max-width: 768px) {
  .box {
    background: #9EFF70;
    color: black;
  }
}
/* @media — условие.
   Стили внутри сработают, только если
   ширина экрана ≤ 768px.
   На более широких экранах
   браузер их проигнорирует. */
      

Внутри @media вы пишете обычные CSS-правила. Они сработают только на экранах шириной до 768 пикселей. На более широких экранах браузер проигнорирует всё, что написано внутри этого блока.

Условие max-width означает если ширина экрана не больше указанного значения. Условие min-width работает наоборот. Если ширина не меньше. На практике min-width используется чаще, потому что соответствует подходу mobile-first, когда базовые стили пишутся для маленького экрана, а через min-width добавляются правила для больших.

Блок 1
Блок 2
Блок 3

<div class="row">
  <div class="col">Блок 1</div>
  <div class="col">Блок 2</div>
  <div class="col">Блок 3</div>
</div>
      

/* Базовые стили — для мобильного */
.row {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
 
/* Расширение — для десктопа */
@media (min-width: 768px) {
  .row {
    flex-direction: row;
  }
  .col {
    flex: 1;
  }
}
/* mobile-first: сначала стопка,
   потом ряд. Добавлять легче,
   чем убирать. */
      

В этом примере базовая раскладка лежит вертикальная стопка. При ширине от 768 пикселей блоки перестраиваются в горизонтальный ряд. Медиа-запросы можно комбинировать через and:

Я меняю цвет в зависимости от ширины окна
≤480px — голубой / 481–1024px — розовый / ≥1025px — красный

<div class="box">Текст...</div>
      

/* Мобильный (базовый) */
.box { background: #7ADFF1; }
 
/* Планшет */
@media (min-width: 481px) and (max-width: 1024px) {
  .box { background: #FE96E4; color: white; }
}
 
/* Десктоп */
@media (min-width: 1025px) {
  .box { background: #FE3904; color: white; }
}
/* and комбинирует условия.
   Три диапазона — три разных стиля. */
      

Условие срабатывает только в диапазоне от 481 до 1024 пикселей, которое представляет собой типичный планшетный размер. Это позволяет создать стили, которые работают только на планшетах, не затрагивая мобильные и десктопные экраны.

Stretch: растягивай

Stretch — самый простой из трёх приёмов. Элемент занимает доступное пространство и тянется вместе с контейнером. Когда окно браузера становится шире, элемент становится шире. Когда уже, он сужается. Структура при этом не меняется, количество колонок остаётся прежним, просто каждая колонка становится чуть шире или уже.

Технически stretch реализуется через относительные единицы: проценты, flex: 1, fr в Grid. Элемент не имеет фиксированной ширины в пикселях, вместо этого он получает долю от родителя.

Основное содержимое (flex: 1 — занимает всё оставшееся)

<div class="layout">
  <aside class="sidebar">Боковая панель</aside>
  <main class="main">Содержимое</main>
</div>
      

.layout {
  display: flex;
  gap: 12px;
}
 
.sidebar {
  width: 160px;
  flex-shrink: 0;
}
 
.main {
  flex: 1;
}
/* sidebar фиксирован, main растягивается.
   На широком экране main шире,
   на узком — уже. Sidebar не меняется. */
      

В этом примере боковая панель фиксирована на 160px, а основное содержимое занимает всё оставшееся пространство через flex: 1. На широком экране main шире, на узком уже, но пропорция сохраняется. Sidebar при этом не меняется — он стабильный якорь.

33.33%
33.33%
33.33%

<div class="cards">
  <div class="card">...</div>
  <div class="card">...</div>
  <div class="card">...</div>
</div>
      

.cards {
  display: flex;
  gap: 12px;
}
 
.card {
  width: 33.33%;
}
/* Три карточки всегда занимают
   равные доли. Сужается окно —
   сужаются карточки. */
      

Три карточки по 33.33% ширины. Сужается окно — сужаются карточки, но их всегда три в ряд.

Stretch работает отлично до определённого предела. Проблема начинается, когда элементы растягиваются слишком сильно. Текстовый блок шириной 100% на экране 2560 пикселей превращается в нечитаемую простыню. Строка текста становится длиной в 200 символов, и глаз теряет начало следующей строки.

Решение — ограничитель max-width.

Текстовый блок растягивается по ширине, но никогда не становится шире 480 пикселей. На узком экране он займёт 100%, на широком — остановится на 480px. Это сохраняет комфортную длину строки для чтения.

<div class="text-block">Текстовый блок...</div>
      

.text-block {
  width: 100%;
  max-width: 480px;
}
/* Stretch с ограничителем.
   Блок тянется, но не бесконечно.
   max-width не даёт тексту растянуться
   до нечитаемой ширины. */
      

Блок тянется по ширине, но останавливается на 480 пикселях. На узком экране он занимает 100%, на широком не расползается. Это stretch с верхней границей, и это один из самых полезных паттернов для текстового содержимого.

Stretch хорошо подходит для раскладки секций и контейнеров. Но для текстовых блоков всегда добавляйте max-width — оптимальная длина строки для чтения составляет 50–75 символов, что соответствует примерно 480–640 пикселям при стандартном размере шрифта.

Scale: масштабируй

Scale — это пропорциональное увеличение или уменьшение всех размеров на странице. Если stretch меняет только ширину элементов, то scale меняет всё: шрифты, отступы, промежутки, размеры декоративных элементов. Композиция остаётся той же, просто становится крупнее или мельче.

Самый прямолинейный способ реализовать scale — задать все размеры в viewport-единицах.

Scale

<section class="poster">
  <div class="circle"></div>
  <div class="rect"></div>
  <h1>Scale</h1>
</section>
      

h1 { font-size: 7vw; }
 
.circle {
  width: 18vw;
  height: 18vw;
}
 
.rect {
  width: 12vw;
  height: 25vw;
}
/* Все размеры в vw — вся композиция
   масштабируется пропорционально.
   Уменьшенная копия самой себя
   на любом экране. */
      

Заголовок 7vw, круг 18vw, прямоугольник 12vw. Вся композиция — уменьшенная или увеличенная копия самой себя при любой ширине окна. Для хаотичных и непрерывных веб-плакатов, где важно сохранить пропорции, этот подход работает хорошо.

Проблема чистого vw — отсутствие границ. На маленьком экране шрифт может стать нечитаемым, на огромном — неприлично крупным. Поэтому на практике scale реализуется через clamp().

Заголовок на clamp()
Текст масштабируется плавно, но никогда не становится меньше 14px и больше 20px. Комфортная читаемость на любом устройстве.
A
B
C

<h1>Заголовок на clamp()</h1>
<p>Текст...</p>
<div class="row">
  <div class="box">A</div>
  <div class="box">B</div>
  <div class="box">C</div>
</div>
      

h1 { font-size: clamp(28px, 5vw, 72px); }
p  { font-size: clamp(14px, 1.5vw, 20px); }
 
.row {
  display: flex;
  gap: clamp(8px, 2vw, 32px);
}
 
.box {
  padding: clamp(12px, 2vw, 32px);
}
/* clamp() работает для всего:
   шрифтов, отступов, промежутков.
   Scale с верхней и нижней границей. */
      

clamp() работает для всего: шрифтов, отступов, промежутков. Заголовок масштабируется от 28 до 72 пикселей. Промежутки между карточками — от 8 до 32 пикселей. Все значения плавно подстраиваются под ширину окна, но остаются в комфортном диапазоне.

Есть ещё один способ реализовать scale, который удобен для блочных структур: менять font-size корневого элемента <html> через media query.

Заголовок (2rem)
Текст абзаца (1rem). Все размеры привязаны к базовому font-size в html.
Блок с padding: 1rem и border-radius: 0.5rem
Измените font-size у html с 16px на 14px и всё уменьшится пропорционально

<h1>Заголовок (2rem)</h1>
<p>Текст абзаца (1rem)</p>
<div class="box">Блок с padding: 1rem</div>
      

html { font-size: 16px; }
 
@media (max-width: 768px) {
  html { font-size: 14px; }
}
 
h1   { font-size: 2rem; }    /* 32px → 28px */
p    { font-size: 1rem; }    /* 16px → 14px */
.box { padding: 1rem; }      /* 16px → 14px */
 
/* Один media query на html —
   и вся типографика + отступы
   масштабируются разом. */
      

Когда вся типографика и отступы заданы в rem, достаточно изменить одно значение в :root, чтобы масштабировать всё разом. На десктопе html { font-size: 16px }, на мобильном html { font-size: 14px } и заголовки, текст, padding, gap уменьшаются пропорционально. Это по сути тот самый scale-фактор, но реализованный через систему rem.

Scale через clamp() и scale через rem + media query. Это два разных инструмента для одной цели. clamp() даёт плавное масштабирование без точек перелома. rem + media query даёт ступенчатое масштабирование в конкретных точках. Для веб-плаката clamp() обычно удобнее, потому что требует меньше media queries и работает более органично.

Switch: переключай

Switch — это перестроение раскладки при достижении определённой ширины экрана. Если stretch и scale работают плавно, то switch скорее похож на скачок. Три колонки превращаются в одну. Горизонтальное меню сворачивается в бургер. Боковая панель прячется. Структура меняется качественно, а не количественно.

Технически switch реализуется двумя способами: через media queries и через CSS Grid с auto-fit.

Колонка 1
Колонка 2
Колонка 3

<div class="row">
  <div class="col">Колонка 1</div>
  <div class="col">Колонка 2</div>
  <div class="col">Колонка 3</div>
</div>
      

.row {
  display: flex;
  gap: 12px;
}
 
.col { flex: 1; }
 
@media (max-width: 600px) {
  .row {
    flex-direction: column;
  }
}
/* На широком три колонки в ряд.
   На узком стопка.
   Одно правило в media query
   полностью меняет раскладку. */
      

Классический switch через media query. На широком экране три колонки стоят в ряд (flex-direction: row по умолчанию). На узком перестраиваются в стопку (flex-direction: column). Одно правило в одном media query и раскладка полностью меняется.

Второй способ — автоматический switch через Grid.

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

<div class="grid">
  <div class="card">Карточка 1</div>
  <div class="card">Карточка 2</div>
  <div class="card">Карточка 3</div>
  <div class="card">Карточка 4</div>
</div>
      

.grid {
  display: grid;
  grid-template-columns:
    repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
}
/* Switch без media queries.
   Браузер сам решает, сколько колонок
   помещается. Каждая не уже 200px. */
      

repeat(auto-fit, minmax(200px, 1fr)) создаёт столько колонок, сколько помещается в контейнер, при условии что каждая не уже 200 пикселей. На широком экране четыре карточки в ряд. Сужаете окно — три, потом две, потом одна. Браузер перестраивает раскладку сам, без единого media query. Это switch, встроенный в Grid.

Switch следует применять осознанно. Каждый media query — самостоятельная раскладка, которую нужно тестировать. Для веб-плаката обычно достаточно одного-двух переключений: десктоп → мобильный, иногда с промежуточным планшетным состоянием.

Что использовать

Подведём итог, привязав три подхода к композиционным структурам, которые мы разбирали ранее.

Блочная структура — здесь работают все три. Stretch для контейнеров и секций (flex: 1, проценты). Scale для типографики и отступов (clamp, rem). Switch для перестроения колонок в стопку на мобильном @media или auto-fit.

Хаотичная структура — в первую очередь scale. Все размеры в vw, позиции в процентах. Композиция масштабируется как единое целое. Switch может потребоваться только для текстовых элементов, которые становятся нечитаемыми на маленьком экране.

Непрерывная структура — аналогично хаотичной, преимущественно scale через vw. Вертикальный скролл сохраняется, элементы пропорционально уменьшаются. Switch применяется точечно для текстовых блоков.

Одноэкранный формат — почти целиком scale. Всё привязано к vw и vh, чтобы плакат занимал ровно один экран на любом устройстве. Switch минимален или отсутствует.

Понравилась статья?

Забыли главное или есть что предложить?
Напишите нам в телеграм

Читайте также