28 Модуль 6 Современный CSS 17 мин урок 3 из 25

Container queries: адаптив по контейнеру, а не по экрану

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

Что ты научишься делать

  • Поймёшь, почему медиазапросов недостаточно для компонентов
  • Настроишь контейнер и напишешь запрос к его ширине
  • Сделаешь карточку, которая перестраивается сама в любом месте страницы

#Компонент не знает, где он окажется

Ты сверстал карточку товара: картинка слева, текст справа. На широком экране красиво.

Потом эту же карточку положили в сайдбар шириной 280 пикселей. Картинка и текст сжались до нечитаемого. Медиазапрос не помогает: экран-то широкий, узкий здесь только контейнер.

Обычный ответ — завести модификатор: .card--narrow. Он работает ровно до того момента, пока кто-то не положит карточку в третье место, где ширина ещё какая-то.

Так не надо

/* Компонент зависит от места, куда его положат */
.card { grid-template-columns: 140px 1fr; }
.sidebar .card { grid-template-columns: 1fr; }
.footer .card { grid-template-columns: 1fr; }
.modal .card { grid-template-columns: 100px 1fr; }

Каждое новое место — новая строка. Компонент перестал быть самостоятельным: чтобы им пользоваться, надо знать все места, где он встречается.

Так надо

/* Компонент решает сам, по своей ширине */
@container card (min-width: 420px) {
  .card { grid-template-columns: 140px 1fr; }
}

Одно правило на все случаи. Положи карточку куда угодно — она разберётся.

#Два шага

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

CSScard.css
/* Шаг 1: обёртка становится контейнером */.card-slot {  container-type: inline-size;  container-name: card;}/* Шаг 2: правило срабатывает по ширине контейнера */@container card (min-width: 420px) {  .card {    grid-template-columns: 140px 1fr;    align-items: center;  }}

container-type: inline-size — следим за шириной. Это самый частый вариант: высота обычно определяется содержимым, и следить за ней опасно (получится замкнутый круг).

container-name необязателен, но с именем понятнее: запрос без имени привяжется к ближайшему контейнеру, а это не всегда тот, что нужен.

Интерактивный разбор доступен при включённом JavaScript.

#Единицы контейнера

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

ЕдиницаОт чего считается
cqw1 % ширины контейнера
cqh1 % высоты контейнера
cqi1 % размера вдоль строки — обычно то же, что cqw
cqmin, cqmaxМеньшая и большая из двух сторон
CSSЗаголовок тянется вместе с карточкой
.card__title {  /* Не меньше 16px, не больше 24px, между ними — по ширине контейнера */  font-size: clamp(1rem, 4cqw, 1.5rem);}
Карточка в трёх контейнерах разом
В песочницу

#Как это выглядит в реальном проекте

В работающей системе компонентов запросы к контейнеру и медиазапросы делят обязанности. Медиазапрос отвечает за страницу: сколько колонок в раскладке, показывать ли сайдбар. Запрос к контейнеру отвечает за компонент: как он собран внутри.

CSSlayout.css
/* Страница решает раскладку — это работа медиазапроса */.page {  display: grid;  gap: 24px;}@media (min-width: 1024px) {  .page { grid-template-columns: 1fr 320px; }}/* Слоты объявлены контейнерами один раз, на всю систему */.page > *,.sidebar > * {  container-type: inline-size;  container-name: slot;}/* Компонент подстраивается сам — и в контенте, и в сайдбаре */@container slot (min-width: 380px) {  .post-card { grid-template-columns: 160px 1fr; }  .post-card__excerpt { display: block; }}@container slot (max-width: 379px) {  /* В узком месте лишнее просто убираем */  .post-card__excerpt { display: none; }}

#Что меняется, когда элемент становится контейнером

container-type — не безобидный флаг. Он включает у элемента ограничение размера по нужной оси: содержимое перестаёт влиять на ширину контейнера.

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

Что былоЧто станет после container-type: inline-size
Блок во всю ширинуНичего не изменится — самый частый и безопасный случай
Элемент, сжимавшийся по содержимомуНачнёт занимать всю доступную ширину
Плавающий или абсолютный блокМожет схлопнуться: ширину больше неоткуда взять
ТаблицаРаскладка таблицы сломается — таблицы контейнерами не делают

#Когда НЕ надо

  • Для раскладки всей страницы. Сколько колонок на странице и нужен ли сайдбар — вопрос про экран, а не про контейнер. Это работа медиазапросов, и она никуда не делась.
  • Когда компонент живёт в одном месте. Шапка сайта всегда во всю ширину. Заводить для неё контейнер — лишний слой без пользы.
  • Следить за высотой без нужды. container-type: size требует, чтобы высота была задана явно, иначе браузер не сможет её посчитать. Ошибиться легко, а отладить трудно; в подавляющем большинстве случаев нужен inline-size.
  • Ради «модно». Контейнер создаёт новый контекст: элементы внутри перестают влиять на размер контейнера по нужной оси. Это может неожиданно сломать соседние элементы.

#Поддержка и фоллбэк

ВозможностьСостояние
@container по размеруВо всех современных браузерах с 2023 года
Единицы cqw, cqiВместе с самими запросами
Именованные контейнерыВместе с самими запросами
Запросы по стилю (style())Появились позже и поддерживаются не везде — на них пока не закладывайся
CSSБезопасная схема
/* Базовый вид — узкий. Он же фоллбэк для старых браузеров */.card {  display: grid;  grid-template-columns: 1fr;}/* Улучшение приходит только там, где запросы понимают */@container card (min-width: 420px) {  .card { grid-template-columns: 140px 1fr; }}

Где спотыкаются почти все

Контейнером назначен сам компонент

Почему Элемент не может отвечать на запрос о собственном контейнере. Правило внутри @container просто не сработает, и причина неочевидна.

Как надо Контейнер — обёртка или слот раскладки. Перестраивается то, что лежит внутри.

Забыт container-type

Почему Одного container-name мало: без типа элемент контейнером не становится, а запрос молча привязывается к другому предку или ни к чему.

Как надо Всегда пиши обе строки, а лучше сокращение: container: card / inline-size;

Взят container-type: size без заданной высоты

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

Как надо Бери inline-size, если нет твёрдой причины следить за высотой.

Запросы к контейнеру заменили медиазапросы полностью

Почему Раскладка страницы и настройки вроде тёмной темы или prefers-reduced-motion к контейнерам отношения не имеют.

Как надо Страница — медиазапросы, компонент — контейнерные. Они дополняют друг друга.

Контейнерный запрос container query
Правило, срабатывающее по размеру элемента-предка, а не окна.
Тип контейнера container-type
За каким размером следить: inline-size — за шириной, size — за обеими сторонами.
Единицы контейнера cqw, cqh, cqi
Проценты от размера контейнера. Позволяют привязать шрифт и отступы к ширине компонента.

Проверь себя

  1. Почему медиазапрос не решает задачу с карточкой в узком сайдбаре?

    Экран может быть широким, а места компоненту досталось мало. Про это медиазапрос ничего не знает.

  2. Что нужно, чтобы <code>@container</code> заработал?

    Без <code>container-type</code> элемент контейнером не становится, и запросу не к чему привязаться.

  3. Может ли элемент отвечать на запрос о своей собственной ширине?

    Иначе получился бы замкнутый круг: стиль менял бы ширину, ширина меняла бы стиль. Поэтому компонент оборачивают.

  4. Что делать со старыми браузерами, которые не знают @container?

    Незнакомое @-правило игнорируется целиком. Если базовый вид рабочий, ничего чинить не нужно.

  5. Что здесь остаётся работой медиазапросов?

    Верных вариантов несколько.

    Всё, что про экран и настройки человека, — медиазапросы. Контейнерные отвечают только за внутреннее устройство компонента.

Задание

Карточка для двух мест сразу

Одна и та же карточка стоит в узком слоте (200px) и в широком, растянутом на всю доступную ширину. Сейчас она везде одинаковая и в широком месте выглядит бедно.

Сделай так, чтобы обёртка стала контейнером с именем card, а начиная с ширины 280px карточка перестраивалась в две колонки 110px 1fr: картинка слева, текст справа. Разметку не трогай.

Открыть в песочнице
    Подсказка 1

    Контейнер объявляется на обёртке: .slot { container-type: inline-size; container-name: card; }

    Подсказка 2

    Запрос пишется так: @container card (min-width: 280px) { .card { grid-template-columns: 110px 1fr; } }

    Показать решение
    HTMLindex.html
    <div class="row">  <div class="slot slot--narrow">    <article class="card">      <div class="media"></div>      <div class="body"><h3>Раф</h3><p>Мягкий вкус</p></div>    </article>  </div>  <div class="slot slot--wide">    <article class="card">      <div class="media"></div>      <div class="body"><h3>Раф</h3><p>Мягкий вкус</p></div>    </article>  </div></div>
    CSSstyle.css
    .slot {  container-type: inline-size;  container-name: card;}@container card (min-width: 280px) {  .card {    grid-template-columns: 110px 1fr;    align-items: center;  }  .media { height: 100%; min-height: 90px; }}.row { display: grid; gap: 14px; }.slot--narrow { inline-size: 200px; }.slot--wide { inline-size: 100%; }.card {  display: grid;  gap: 10px;  padding: 12px;  background: #f5f5fa;  border-radius: 12px;}.media {  height: 70px;  background: linear-gradient(135deg, #7c5cff, #b06cff);  border-radius: 8px;}.body h3 { margin: 0 0 4px; font-size: 16px; }.body p { margin: 0; font-size: 13px; color: #5b6070; }

    Проверь себя