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

Зачем использовать контейнерные запросы?
До появления контейнерных запросов основным способом адаптации интерфейса под разные экраны были медиа-запросы (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 и сложной поддержке. Чтобы уменьшить связность стилей и сделать компоненты самодостаточными, лучше измерять не окно, а контейнер, в котором расположен компонент.
Как создать контейнерный запрос
- Сделайте элемент контейнером, добавив правило container-type. Часто используется значение inline-size — контейнер будет измеряться по ширине.
main, .sidebar {
container-type: inline-size;
}- После этого внутри других блоков можно использовать директиву @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) и проверить поведение фолбэка.
Рекомендации по миграции существующего кода
- Проанализируйте, какие компоненты зависят от ширины окна, а какие должны зависеть от контейнера.
- Для повторно используемых компонентов сначала добавьте container-type и простые @container правила.
- Оставьте критичные медиа-запросы для глобальной перестройки макета.
- Покрывайте изменения тестами и постепенным развёртыванием.
Быстрая чеклиста для разработчика
- Добавил 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 по миграции конкретного проекта, я могу подготовить пошаговую инструкцию и шаблон файлов.
Похожие материалы
Как устроить идеальную вечеринку для просмотра ТВ
Как распаковать несколько RAR‑файлов сразу
Приватный просмотр в Linux: как и зачем
Windows 11 не видит iPod — способы исправить
PS5: как настроить игровые пресеты