$ sudo teach IT

Медиазапросы смотрят на ширину всего экрана. Но представьте карточку товара, которая используется и в узкой боковой колонке, и в широкой основной области. По размеру экрана обе выглядят одинаково, хотя места вокруг у них совершенно разное — и в узкой колонке карточку хотелось бы показать компактнее. Медиазапрос тут бессилен: он не знает, сколько места у конкретного блока. Эту задачу решают контейнерные запросы @container — они позволяют компоненту подстраиваться под размер своего родителя, а не всего экрана. Это делает компоненты по-настоящему переиспользуемыми.

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

Что уже умеем / что добавим

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

Что вы узнаете в рамках статьи

  • Чем контейнерные запросы отличаются от медиазапросов.
  • Как объявить контейнер через container-type.
  • Как писать правило @container по ширине родителя.
  • Когда контейнерные запросы удобнее медиазапросов.

Ключевые термины

  • Контейнерный запрос — правило, срабатывающее по размеру родителя-контейнера.
  • container-type — свойство, объявляющее элемент контейнером для запросов.
  • @container — правило, применяющее стили при условии на размер контейнера.
  • container-name — необязательное имя контейнера для адресных запросов.

Медиа против контейнера

Разница проста, но принципиальна. Медиазапрос @media спрашивает: «какова ширина экрана?» Контейнерный запрос @container спрашивает: «сколько места у моего родителя?» Первый смотрит наружу, на окно; второй — на ближайший контейнер вокруг компонента.

Это меняет саму логику. Один и тот же компонент, помещённый в разные места страницы, с медиазапросами выглядел бы одинаково (экран-то один). С контейнерными запросами он подстроится под каждое место: узкий контейнер — компактный вид, широкий — развёрнутый. Компонент становится независимым и переиспользуемым — его можно вставить куда угодно, и он сам примет верную форму.

Объявляем контейнер

Чтобы по элементу можно было задавать запросы, его нужно объявить контейнером свойством container-type. Чаще всего используют значение inline-size — «отслеживать ширину»:

.card-wrap {
  container-type: inline-size;
}

Теперь .card-wrap — контейнер, и вложенные в него элементы могут реагировать на его ширину. Важно: запросы будут смотреть именно на этот объявленный контейнер, а не на экран. Обычно контейнером делают обёртку вокруг компонента, а сам компонент (карточку) адаптируют внутри. По желанию контейнеру можно дать имя через container-name, чтобы адресовать запросы конкретно ему, — но для одного контейнера это не обязательно.

Пишем @container

Правило @container очень похоже на @media, только условие относится к контейнеру, а не к экрану:

.card {
  display: grid;
  grid-template-columns: 1fr;
}

@container (min-width: 400px) {
  .card {
    grid-template-columns: 120px 1fr;
  }
}

Читается так: «если контейнер шире 400 пикселей, покажи карточку в две колонки — картинка и текст рядом». Пока родитель уже 400px, карточка остаётся в один столбец. Обратите внимание: мы нигде не упоминаем размер экрана — важна только ширина контейнера .card-wrap. Поставьте такую карточку в узкую боковую колонку — она свернётся в столбик; поставьте в широкую область — развернётся в две колонки. И всё это на одном и том же экране.

Когда что выбирать

Медиазапросы и контейнерные запросы не заменяют друг друга, а дополняют. Для общего каркаса страницы — расположения крупных зон, глобальных отступов, числа основных колонок — по-прежнему удобны медиазапросы: они про экран в целом.

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

Применяем в проекте

Сделаем карточку услуги самонастраивающейся. Обернём её в контейнер и опишем: в узком месте содержимое идёт в столбик, в широком — картинка слева, текст справа. Теперь карточку можно ставить и в основную сетку, и в узкую колонку — она подстроится сама.

.card-wrap {
  container-type: inline-size;
}

.card {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

@container (min-width: 400px) {
  .card {
    grid-template-columns: 140px 1fr;
    align-items: center;
  }
}

Поместите такую карточку в широкую область — картинка встанет слева, текст справа. Перенесите её в узкую колонку — та же карточка свернётся в аккуратный столбик, хотя размер экрана не менялся. Компонент стал по-настоящему переиспользуемым: он реагирует на своё окружение, а не на окно. Это большой шаг к профессиональной, компонентной вёрстке.

Частые ошибки

  • Забыли container-type.
    Без объявления контейнера правило @container не на что опереться и не сработает.
  • Запрос к самому себе.
    Контейнером делают родителя-обёртку, а адаптируют вложенный компонент; элемент не может опрашивать собственный размер как контейнер.
  • Подмена медиазапросов.
    Для глобального каркаса страницы @media обычно уместнее; @container — для компонентов.
  • Ожидание реакции на экран.
    @container смотрит на родителя, а не на окно; для условий по экрану нужен @media.

Итоги

Замечательно — вы освоили один из самых современных инструментов вёрстки! Вы поняли, чем контейнерные запросы отличаются от медиазапросов, научились объявлять контейнер через container-type и писать правила @container по ширине родителя. Ваша карточка услуги теперь подстраивается под место, куда её поставили, — она стала самостоятельным переиспользуемым компонентом. Это тот уровень, на котором думают в настоящих дизайн-системах.

Отличная работа! В следующем уроке мы добавим гибкости самим размерам: изучим единицы %, vw/vh и функции clamp(), min(), max(), которые позволяют элементам плавно масштабироваться без единого медиазапроса.

На следующий урок