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

Контейнерные запросы 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
Автор
Редакция

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

Несколько аккаунтов Skype: Multi Skype Launcher
Программное обеспечение

Несколько аккаунтов Skype: Multi Skype Launcher

Журнал для работы: повысить продуктивность
Productivity

Журнал для работы: повысить продуктивность

Персональные звуки уведомлений на Android
Android.

Персональные звуки уведомлений на Android

Скачивание шоу Hulu для офлайн‑просмотра
Стриминг

Скачивание шоу Hulu для офлайн‑просмотра

Microsoft Start: персонализированная новостная лента
Новости

Microsoft Start: персонализированная новостная лента

Как изменить имя в Epic Games быстро
Гайды

Как изменить имя в Epic Games быстро