Что такое контейнерные запросы — медиазапрос, который смотрит не на экран

Смотри, тонкая штука. Ты сделал карточку товара: на широком экране — картинка слева, текст справа, на узком — всё в столбик. Написал медиазапрос, проверил на телефоне и на мониторе, всё отлично.
А потом эту же карточку положили в сайдбар. Монитор по-прежнему широкий — значит, по мнению @media, «места много». И карточка честно раскладывается в две колонки внутри полосы шириной 280 пикселей. Каша.
Проблема не в твоём коде. Медиазапрос в принципе не умеет мерить то, что нужно.
Медиазапрос меряет окно, а не место
@media (min-width: 600px) отвечает на вопрос «какой ширины окно браузера». Всегда только на него. А компонент живёт не в окне — он живёт в своей колонке, ячейке или карточке, и ширина у неё своя.
Пока страница была одной колонкой по центру, разница была незаметна: ширина контента ≈ ширина окна. Как только появились сайдбары, сетки и переиспользуемые компоненты, разница стала критичной. Один и тот же блок должен выглядеть по-разному в трёх местах одной страницы — а окно у них у всех общее.
Контейнерные запросы решают ровно это: они спрашивают про ближайшего подходящего родителя, а не про экран.
Как это выглядит в коде
Нужны две вещи. Сначала объявляешь родителя контейнером:
.card-slot {
container-type: inline-size;
}
inline-size значит «следи за шириной». Дальше пишешь условие — почти как медиазапрос, но со словом container:
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
}
}
Читается буквально: «если у контейнера, в котором лежит эта карточка, ширина от 400 пикселей — разложи её в две колонки». Теперь один и тот же компонент сам подстраивается и в сайдбаре, и в центре, и в модалке. Ты его больше не настраиваешь под каждое место.
Контейнеров может быть много, поэтому им дают имена — container-name: sidebar и потом @container sidebar (min-width: 400px). Пригодится, когда контейнеры вложены друг в друга.
Есть и свои единицы измерения: cqi — 1% ширины контейнера (и cqw, cqh, cqb для других осей). Удобно для шрифта, который должен расти вместе с карточкой, а не с окном.
Две ловушки, о которых молчат туториалы
Первая: нельзя запросить сам себя. @container смотрит на ближайшего предка-контейнер, а не на элемент, к которому применяется правило. Написать container-type и условие на одном и том же блоке не выйдет — нужна обёртка. Это не баг, это защита от бесконечного цикла: правило меняло бы размер, из-за которого оно же перестало бы срабатывать.
Вторая: контейнер может схлопнуться. container-type: inline-size включает так называемое сдерживание (containment) — браузер перестаёт брать размер элемента из его содержимого по той самой ширине. Если у контейнера нет ни явного размера, ни размера от родителя, он может схлопнуться в ноль. Практический совет: делай контейнером обычный блочный элемент, который и так растягивается на ширину родителя, — тогда всё в порядке.
И третье, про запас: container-type со значением size или inline-size создаёт контекст наложения. Если после включения контейнерных запросов подсказка вдруг уехала под соседний блок — ты теперь знаешь, где искать.
Как перевести готовый компонент на @container
Переписывать всё разом не надо. Возьми один компонент, который уже поехал в узком месте, и сделай три шага.
- Найди обёртку. У компонента должен быть родитель, который отвечает за его место на странице: ячейка сетки, колонка, слот в сайдбаре. Нет такого — добавь пустой блок вокруг. Ему и достанется
container-type: inline-size. - Замени условие. В стилях компонента меняешь
@media (min-width: 600px)на@container (min-width: 400px)— и сразу пересматриваешь число. Это важный момент: старый порог был про ширину окна, новый — про ширину контейнера. Обычно новое число заметно меньше старого. - Проверь в двух местах сразу. Положи компонент на страницу дважды: в основную колонку и в узкий блок рядом. Если оба выглядят правильно на одном и том же экране — перевод удался. Раньше такой тест было просто невозможно пройти.
Когда это реально нужно
Не везде. Разложить саму страницу — по-прежнему работа @media: сколько колонок в макете, показывать ли боковое меню. Это решения про устройство и экран.
@container — про компонент: карточку, виджет, блок комментариев, плитку в галерее. Признак, что пора: ты ловишь себя на классах вроде card--narrow и card--wide, которые ставишь руками в зависимости от того, куда кладёшь блок. Это и есть ручная замена контейнерному запросу.
Отдельно про ИИ-помощников. Попроси нейросеть «сделать карточку адаптивной» — почти наверняка получишь @media: этого кода в мире написано в тысячи раз больше. Если компонент переиспользуемый, стоит прямо попросить контейнерный запрос и указать, какой элемент будет контейнером. Это ровно тот случай, когда точная формулировка задачи экономит полчаса правок.
Поддержка давно не проблема: Chrome 105, Firefox 110, Safari 16 — то есть во всех актуальных браузерах с 2022–2023 годов.
Коротко:
@media— про экран,@container— про место, в которое тебя положили.
Можно ли применить container-type к самому элементу, который стилизуешь?
Нет. Условие проверяется у предка. Оберни компонент в дополнительный блок и сделай контейнером его — это стандартный приём, а не костыль.
Чем cqi отличается от vw?
vw — 1% ширины окна, cqi — 1% ширины контейнера. В сайдбаре шириной 280 пикселей 100cqi это 280 пикселей, а 100vw — вся ширина монитора. Для компонентов почти всегда нужен первый.
Заменяют ли контейнерные запросы медиазапросы?
Нет, они делят работу. Каркас страницы — @media, внутреннее устройство компонентов — @container. В одном проекте спокойно живут оба; как строить каркас, разбираем в Flexbox или Grid.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





