Skip to content
PПромтбук
RUEN
02Адаптив

Container queries: где @container побеждает @media

Когда переходить с viewport-брейкпойнтов на контейнерные. Конвенции именования, fallback, реальные сценарии.

Действуй как senior CSS-инженер. Переведи {{component}} с viewport-брейкпойнтов на container queries. Baseline: {{browser_baseline}}.

1. Когда @container побеждает @media

Container queries нужны, если компонент не знает, в какой колонке он окажется:

  • Card в галерее — может лежать в 1, 2, 3 или 4 колонках. На 1280px viewport карточка может быть и 1200px, и 280px шириной.
  • Sidebar widget — один и тот же компонент в правой колонке (320px) и в полноширинном модальном окне (900px).
  • Embed / iframe виджет — встроен на чужой сайт, viewport ≠ ширина виджета.
  • CMS-блок — редактор выбирает 1/2/3 колонки в визуальном конструкторе.
  • Dashboard tile — пользователь сам растягивает плитку drag-and-drop.

Где @media всё ещё уместен: глобальный layout страницы (header, footer, основная сетка), navigation patterns (hamburger vs полное меню), общая типографика body.

2. Базовая разметка

/* Объявляем контейнер */
.card-shell {
  container-type: inline-size;
  container-name: card;
}

/* Стилизуем содержимое в зависимости от ширины контейнера */
@container card (min-width: 480px) {
  .card { display: grid; grid-template-columns: 160px 1fr; gap: 1.5rem; }
}

@container card (min-width: 720px) {
  .card { grid-template-columns: 240px 1fr 200px; }
}

Важно:

  • container-type: inline-size — отслеживаем только ширину (дёшево). size — и ширину, и высоту (дороже, нужно только для height-based logic).
  • container-name обязателен, если на странице несколько вложенных контейнеров.
  • Свойства самого контейнера (width, padding) нельзя менять внутри его же @container — это вызовет infinite loop. Меняй только потомков.

3. Naming convention

Имя контейнера — это контракт. Назови по роли, не по размеру:

container-name: card;          /* role */
container-name: sidebar;       /* role */
container-name: product-tile;  /* role */

/* НЕ так */
container-name: small;         /* размер — будет врать */
container-name: c1;            /* непонятно */

Префиксы для команд: ui-card, shop-product-tile, dash-widget — чтобы не было коллизий между фичами.

4. Fallback стратегия

Для Safari ≤ 15 и старого Chrome нужен fallback. Три подхода:

A. Прогрессивное улучшение через @supports

.card { /* mobile-first viewport-based стили */ }
@media (min-width: 768px) { .card { /* tablet */ } }

@supports (container-type: inline-size) {
  .card-shell { container-type: inline-size; container-name: card; }
  .card { /* сбрасываем viewport-based, переключаемся на @container */ }
  @container card (min-width: 480px) { .card { /* ... */ } }
}

B. Polyfill (container-query-polyfill) — для критичных публичных страниц, ~6KB gzip, наблюдает за resize контейнеров через ResizeObserver. Не использовать на 1000+ контейнерах на странице.

C. JS-based опциональный класс

const ro = new ResizeObserver(([entry]) => {
  const w = entry.contentRect.width;
  entry.target.dataset.size = w < 480 ? 'sm' : w < 720 ? 'md' : 'lg';
});
ro.observe(cardShell);

Селекторы вида [data-size="lg"] .card работают везде.

5. Container query units

cqw, cqh, cqi, cqb, cqmin, cqmax — относительные к контейнеру, не к viewport. Идеально для fluid spacing внутри карточки:

@container card (min-width: 400px) {
  .card__title { font-size: clamp(1rem, 4cqi, 1.5rem); }
  .card__padding { padding: 4cqi; }
}

6. Формат вывода

## Refactor: viewport → container

### Before (@media)
[фрагмент кода]

### After (@container)
[фрагмент кода]

### Контейнерная карта
| Container | Type | Breakpoints | Где живёт |
|---|---|---|---|
| card | inline-size | 480, 720 | gallery, sidebar, modal |

### Fallback
[стратегия для {{browser_baseline}}]

Anti-patterns

  • container-type: size без причины — браузер пересчитывает и width, и height на каждый resize, дороже в 2-3 раза.
  • ❌ Изменение width контейнера внутри его же @container — infinite loop, страница виснет.
  • ❌ Container query без container-name при вложенных контейнерах — стиль уходит в ближайшего родителя, не туда, куда ждёшь.
  • ❌ Полная замена @media на @container в одну ночь — не трогай глобальный layout, header, нав. Это viewport-territory.
  • ❌ Имя контейнера по размеру (small, large) — через месяц компонент шире, имя врёт, рефакторить страшно.
  • ❌ Polyfill на админке с 500+ карточками — ResizeObserver на каждый узел убивает FPS.
  • ❌ Отказ от mobile-first — забыли базовые стили без @container, при отсутствии поддержки страница без layout.
  • ❌ Container queries для глобальной типографики body — это работа clamp() и viewport units, а не контейнеров.
К подразделу «Адаптив»
Похожие промты