Гид по технологиям

Контейнерные запросы CSS — адаптивные компоненты

6 min read Frontend Обновлено 06 Dec 2025
Контейнерные запросы CSS — адаптивные компоненты
Контейнерные запросы CSS — адаптивные компоненты

Человек работает за 15-дюймовым MacBook Pro

Зачем использовать контейнерные запросы?

До появления контейнерных запросов основным способом адаптации интерфейса под разные экраны были медиа-запросы (media queries). Но у них есть ключевое ограничение: они могут реагировать только на размер окна (viewport), а не на размер ближайшего контейнера. Это создаёт сложности при работе с модульными компонентами и сайдбарами.

Контейнерные запросы решают эту проблему: теперь можно писать правила, которые зависят от реального размера контейнера — блока, внутри которого находится компонент. Это особенно удобно в компонентных фреймворках (React, Vue, Svelte), где компоненты часто используются в разных контекстах.

Важно: контейнерные запросы не отменяют медиа-запросы. Они дополняют их и облегчают создание самодостаточных, переносимых компонентов.

Пример репозитория и быстрый запуск

Исходный код, использованный в статье, доступен в репозитории на GitHub и распространяется под MIT-лицензией. Клонируйте репозиторий и запустите локальный сервер (например, через npm, npx или простую отдачу статических файлов), чтобы увидеть демо-страницу.

После запуска вы увидите страницу, похожую на изображение ниже:

Снимок экрана страницы в полноэкранном режиме

Страница содержит сетку из двух колонок: основное содержимое и сайтбар. На больших экранах карточки выглядят нормально, но сам сайтбар меньше и визуально «сжат».

Проблема: поведение при сжатии окна

Макет адаптивен: при уменьшении ширины браузера карточки переходят в вертикальную колонку — изображение становится над контентом:

Снимок экрана страницы в сжатом виде

Это поведение обеспечивается медиа-запросами, которые проверяют ширину окна. Простой пример медиа-запроса, переключающего направление Flexbox при ширине окна меньше 800px:

@media (max-width: 800px) {
  .card {
    flex-direction: column;
  }
  .card-header {
    width: 100%;
  }
}

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

Почему медиа-запросы не всегда удобны

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

Как создать контейнерный запрос

  1. Сделайте элемент контейнером, добавив правило container-type. Часто используется значение inline-size — контейнер будет измеряться по ширине.
main, .sidebar {
  container-type: inline-size;
}
  1. После этого внутри других блоков можно использовать директиву @container, которая похожа на @media, но измеряет текущий контейнер (не окно):
@container (max-width: 500px) {
  .card {
    flex-direction: column;
  }
  .card-header {
    width: 100%;
  }
}

Поведение аналогично медиа-запросу, но отсчёт идёт от размеров ближайшего контейнера, объявленного через container-type.

Снимок экрана страницы с контейнерными запросами

На изображении сайдбар принимает вертикальную форму, потому что его ширина меньше 500px, а основная колонка остаётся горизонтальной — её контейнер шире 500px.

Именование контейнеров и приоритет ближайшего контейнера

Правило @container ищет ближайший контейнер по иерархии DOM. Если карточка вложена в main и в body, и оба являются контейнерами, то будет использован ближайший (обычно main).

Если вы хотите явно ориентироваться на конкретный контейнер, задайте ему имя через container-name и укажите его в @container:

body {
  container-type: inline-size;
  container-name: page;
}

@container page (max-width: 1000px) {
  /* Правила, реагирующие на ширину контейнера body с именем page */
}

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

Единицы контейнера: cqw и cqh

Контейнерные единицы похожи на единицы окна (vw, vh), но привязаны к размерам контейнера. Основные единицы:

  • cqw — процент от ширины контейнера (container query width). 1cqw = 1% ширины контейнера.
  • cqh — процент от высоты контейнера (container query height). 1cqh = 1% высоты контейнера.

Пример использования:

.card-image {
  width: 50cqw; /* 50% от ширины контейнера */
  height: auto;
}

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

Поддержка и детектирование возможностей

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

@supports (container-type: inline-size) {
  /* Стили с контейнерными запросами */
}

/* Фолбэк для старых браузеров */
@supports not (container-type: inline-size) {
  /* Медиазапросы или JavaScript-решение */
}

Если приложение должно работать в очень старых браузерах, рассмотрите полифилы или JS-адаптации (см. раздел «Альтернативные подходы»).

Когда контейнерные запросы не подходят

  • Если макет зависит исключительно от размеров окна (например, глобальная перестановка колонок), медиа-запросы остаются естественным выбором.
  • Если нужно поддержать очень старые браузеры без полифилей, контейнерные запросы могут быть недоступны.
  • Когда поведение зависит не от размеров, а от состояния (динамические изменения данных), возможно, лучше управлять стилями через классы/JS.

Важно: контейнерные запросы — мощный инструмент, но не универсальный. Смешивайте подходы в зависимости от задачи.

Альтернативы и сочетание с JavaScript

Если нужна точечная логика или вы хотите реагировать на изменение размеров немедленно в коде, можно использовать ResizeObserver в JavaScript:

const ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    const width = entry.contentRect.width;
    const el = entry.target;
    if (width < 500) el.classList.add('is-narrow');
    else el.classList.remove('is-narrow');
  }
});
ro.observe(document.querySelector('.card'));

Преимущество ResizeObserver — гибкость и совместимость с кастомной логикой. Недостаток — больше JavaScript и ручное управление классами.

Шпаргалка: синтаксис и наиболее полезные паттерны

  • Объявить контейнер по ширине:
.container {
  container-type: inline-size;
}
  • Использовать именованный контейнер:
.container { container-type: inline-size; container-name: layout; }
@container layout (max-width: 720px) { ... }
  • Проверка поддержки:
@supports (container-type: inline-size) { /* use container queries */ }
  • Пример с единицами контейнера:
.title { font-size: 3cqw; }

Контроль качества и тесты (критерии приёмки)

  • Компонент корректно меняет раскладку при уменьшении ширины своего контейнера (например, переход на column при width <= 500px).
  • Вложенные контейнеры используют ближайший родительский контейнер по ожиданиям команды.
  • В старых браузерах присутствует корректный фолбэк: медиа-запросы или JavaScript-альтернатива.
  • Единицы cqw/cqh не приводят к неожиданному переполнению в контейнере с фиксированной высотой.

Тестовые кейсы:

  • Вложить компонент в узкий сайтбар и в широкую колонку, убедиться в независимом поведении.
  • Изменить имя контейнера и проверить, что @container с указанным именем действует ожидаемо.
  • Отключить поддержку container-type (через @supports not) и проверить поведение фолбэка.

Рекомендации по миграции существующего кода

  1. Проанализируйте, какие компоненты зависят от ширины окна, а какие должны зависеть от контейнера.
  2. Для повторно используемых компонентов сначала добавьте container-type и простые @container правила.
  3. Оставьте критичные медиа-запросы для глобальной перестройки макета.
  4. Покрывайте изменения тестами и постепенным развёртыванием.

Быстрая чеклиста для разработчика

  • Добавил container-type к контейнеру
  • Проверил ближайший контейнер через DevTools
  • Использую @container с понятными порогами
  • Добавил @supports фолбэк
  • Протестировал в реальных сценариях (сайдбар, карточки, модальные окна)

Decision flow (упрощённая логика выбора)

flowchart TD
  A[Нужно ли адаптировать компонент?] --> B{Зависит от размера окна?}
  B -- Да --> C[Использовать медиа-запросы]
  B -- Нет --> D{Нужно ли зависеть от контейнера?}
  D -- Да --> E[Добавить container-type и @container]
  D -- Нет --> F[Управлять классами/JS]
  E --> G[Добавить фолбэк через @supports]
  C --> G
  F --> G

Короткий глоссарий

  • container-type — свойство, делающие элемент контейнером для контейнерных запросов.
  • container-name — имя контейнера, по которому можно адресовать @container.
  • @container — директива, похожая на @media, но измеряющая контейнер.
  • inline-size — измерение по основной оси (обычно ширина) для container-type.
  • cqw / cqh — единицы, зависящие от ширины / высоты контейнера.

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

  • При разработке библиотек UI-компонентов (карточки, списки, виджеты), которые будут использоваться в разных местах.
  • В лэйаутах с комбинированными колонками (основной контент + сайдбар), где части страницы имеют разную ширину.
  • В компонентах, которые нужно встроить в сторонние страницы без ломки внешнего макета.

Альтернативные подходы и когда их предпочесть

  • ResizeObserver + JS — когда нужна сложная логика и взаимодействие с данными.
  • CSS-переменные + медиа-запросы — если вы хотите централизованно управлять точками перелома, но они применимы глобально.
  • Комбинация медиа-запросов и контейнерных запросов — часто лучший вариант: глобальная адаптация окна + локальная адаптация компонентов.

Итог

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

Важно: не заменяйте все медиа-запросы контейнерными — выбирайте инструмент по задаче.

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

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как устроить идеальную вечеринку для просмотра ТВ
Развлечения

Как устроить идеальную вечеринку для просмотра ТВ

Как распаковать несколько RAR‑файлов сразу
Инструменты

Как распаковать несколько RAR‑файлов сразу

Приватный просмотр в Linux: как и зачем
Приватность

Приватный просмотр в Linux: как и зачем

Windows 11 не видит iPod — способы исправить
Руководство

Windows 11 не видит iPod — способы исправить

PS5: как настроить игровые пресеты
Консоли

PS5: как настроить игровые пресеты

Как переключить камеру в Omegle на iPhone и Android
Руководство

Как переключить камеру в Omegle на iPhone и Android