Media queries отвечают на вопрос «какой размер у окна?». Container queries отвечают на вопрос «какой размер у моего контейнера?». Это принципиальное отличие: компонент теперь адаптируется к своему окружению, а не к размеру экрана.
Представь карточку товара. В sidebar она занимает 200px, на главной странице — 400px. С media queries ты не можешь сделать разный стиль для одного и того же компонента в разных местах страницы — breakpoint один на весь экран.
Container queries решают это элегантно.
/* 1. Объявить контейнер */
.card-wrapper {
container-type: inline-size; /* Отслеживать ширину */
container-name: card; /* Необязательное имя */
}
/* Сокращение */
.card-wrapper {
container: card / inline-size;
}
/* 2. Применить стили по размеру контейнера */
@container (min-width: 400px) {
.card {
flex-direction: row; /* Горизонтальная карточка в широком контейнере */
}
}
/* Именованный контейнер */
@container card (min-width: 400px) {
.card__image {
width: 40%;
}
}/* inline-size — отслеживать только ширину (чаще всего это нужно) */
.wrapper { container-type: inline-size; }
/* size — отслеживать и ширину, и высоту */
.widget { container-type: size; }
/* normal — не создавать контейнер (по умолчанию) */
.div { container-type: normal; }Аналог vw/vh, но относительно контейнера:
@container (min-width: 300px) {
.card__title {
font-size: 5cqi; /* 5% от inline-size контейнера */
}
}
/* cqi — container query inline (ширина в ltr) */
/* cqb — container query block (высота в ltr) */
/* cqw, cqh — явно ширина/высота */
/* cqmin, cqmax — меньшее/большее из cqw и cqh *//* Media query — реагирует на экран */
@media (min-width: 768px) {
.card { flex-direction: row; }
/* Но что если карточка в sidebar шириной 300px на экране 1200px? */
}
/* Container query — реагирует на контейнер */
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) {
.card { flex-direction: row; }
/* Работает правильно в любом месте страницы */
}.card-container {
container: product-card / inline-size;
}
.product-card {
display: flex;
flex-direction: column;
gap: 12px;
}
/* Узкая карточка (менее 350px) */
.product-card__image { width: 100%; }
/* Широкая карточка — горизонтальная раскладка */
@container product-card (min-width: 350px) {
.product-card {
flex-direction: row;
align-items: center;
}
.product-card__image {
width: 40%;
flex-shrink: 0;
}
}
/* Очень широкая — ещё больше деталей */
@container product-card (min-width: 600px) {
.product-card__description { display: block; }
.product-card__rating { display: flex; }
}Container queries поддерживаются во всех современных браузерах с 2023 года. Для старых браузеров используй прогрессивное улучшение: стили без @container работают как базовые, стили внутри @container — как улучшение.
Симуляция container query matching: сравниваем ширину элемента с breakpoints и вызываем коллбэки
// Симуляция Container Queries через ResizeObserver (как работает браузер внутри)
const container = document.createElement('div')
container.style.cssText = `
width: 300px;
border: 2px solid #7b2ff7;
padding: 16px;
font-family: sans-serif;
border-radius: 8px;
resize: horizontal;
overflow: auto;
`
document.body.appendChild(container)
const card = document.createElement('div')
card.id = 'adaptive-card'
card.style.cssText = 'background: #f0f4ff; padding: 12px; border-radius: 6px;'
card.textContent = 'Адаптивная карточка'
container.appendChild(card)
const info = document.createElement('pre')
info.style.cssText = 'margin-top: 8px; font-size: 12px; background: #1a202c; color: #a0aec0; padding: 8px; border-radius: 4px;'
document.body.appendChild(info)
// Набор правил, аналогичных @container
const containerRules = [
{
minWidth: 0,
apply() {
card.style.flexDirection = 'column'
card.style.display = 'block'
card.style.background = '#fef3c7'
return 'xs: вертикальная раскладка (0px+)'
}
},
{
minWidth: 300,
apply() {
card.style.display = 'flex'
card.style.flexDirection = 'row'
card.style.gap = '12px'
card.style.background = '#dbeafe'
return 'sm: горизонтальная раскладка (300px+)'
}
},
{
minWidth: 500,
apply() {
card.style.background = '#d1fae5'
return 'lg: широкая карточка (500px+)'
}
},
]
function applyContainerRules(element) {
const width = element.offsetWidth
let activeRule = null
for (const rule of containerRules) {
if (width >= rule.minWidth) {
activeRule = rule
}
}
if (activeRule) {
const desc = activeRule.apply()
info.textContent = `Ширина контейнера: ${width}px\nАктивное правило: ${desc}`
}
}
// Применяем при изменении размера
const ro = new ResizeObserver(() => applyContainerRules(container))
ro.observe(container)
applyContainerRules(container)
console.log('Попробуй растянуть контейнер — он имеет resize: horizontal')
console.log('Container queries работают именно так: ResizeObserver + условные стили')Media queries отвечают на вопрос «какой размер у окна?». Container queries отвечают на вопрос «какой размер у моего контейнера?». Это принципиальное отличие: компонент теперь адаптируется к своему окружению, а не к размеру экрана.
Представь карточку товара. В sidebar она занимает 200px, на главной странице — 400px. С media queries ты не можешь сделать разный стиль для одного и того же компонента в разных местах страницы — breakpoint один на весь экран.
Container queries решают это элегантно.
/* 1. Объявить контейнер */
.card-wrapper {
container-type: inline-size; /* Отслеживать ширину */
container-name: card; /* Необязательное имя */
}
/* Сокращение */
.card-wrapper {
container: card / inline-size;
}
/* 2. Применить стили по размеру контейнера */
@container (min-width: 400px) {
.card {
flex-direction: row; /* Горизонтальная карточка в широком контейнере */
}
}
/* Именованный контейнер */
@container card (min-width: 400px) {
.card__image {
width: 40%;
}
}/* inline-size — отслеживать только ширину (чаще всего это нужно) */
.wrapper { container-type: inline-size; }
/* size — отслеживать и ширину, и высоту */
.widget { container-type: size; }
/* normal — не создавать контейнер (по умолчанию) */
.div { container-type: normal; }Аналог vw/vh, но относительно контейнера:
@container (min-width: 300px) {
.card__title {
font-size: 5cqi; /* 5% от inline-size контейнера */
}
}
/* cqi — container query inline (ширина в ltr) */
/* cqb — container query block (высота в ltr) */
/* cqw, cqh — явно ширина/высота */
/* cqmin, cqmax — меньшее/большее из cqw и cqh *//* Media query — реагирует на экран */
@media (min-width: 768px) {
.card { flex-direction: row; }
/* Но что если карточка в sidebar шириной 300px на экране 1200px? */
}
/* Container query — реагирует на контейнер */
.card-wrapper { container-type: inline-size; }
@container (min-width: 400px) {
.card { flex-direction: row; }
/* Работает правильно в любом месте страницы */
}.card-container {
container: product-card / inline-size;
}
.product-card {
display: flex;
flex-direction: column;
gap: 12px;
}
/* Узкая карточка (менее 350px) */
.product-card__image { width: 100%; }
/* Широкая карточка — горизонтальная раскладка */
@container product-card (min-width: 350px) {
.product-card {
flex-direction: row;
align-items: center;
}
.product-card__image {
width: 40%;
flex-shrink: 0;
}
}
/* Очень широкая — ещё больше деталей */
@container product-card (min-width: 600px) {
.product-card__description { display: block; }
.product-card__rating { display: flex; }
}Container queries поддерживаются во всех современных браузерах с 2023 года. Для старых браузеров используй прогрессивное улучшение: стили без @container работают как базовые, стили внутри @container — как улучшение.
Симуляция container query matching: сравниваем ширину элемента с breakpoints и вызываем коллбэки
// Симуляция Container Queries через ResizeObserver (как работает браузер внутри)
const container = document.createElement('div')
container.style.cssText = `
width: 300px;
border: 2px solid #7b2ff7;
padding: 16px;
font-family: sans-serif;
border-radius: 8px;
resize: horizontal;
overflow: auto;
`
document.body.appendChild(container)
const card = document.createElement('div')
card.id = 'adaptive-card'
card.style.cssText = 'background: #f0f4ff; padding: 12px; border-radius: 6px;'
card.textContent = 'Адаптивная карточка'
container.appendChild(card)
const info = document.createElement('pre')
info.style.cssText = 'margin-top: 8px; font-size: 12px; background: #1a202c; color: #a0aec0; padding: 8px; border-radius: 4px;'
document.body.appendChild(info)
// Набор правил, аналогичных @container
const containerRules = [
{
minWidth: 0,
apply() {
card.style.flexDirection = 'column'
card.style.display = 'block'
card.style.background = '#fef3c7'
return 'xs: вертикальная раскладка (0px+)'
}
},
{
minWidth: 300,
apply() {
card.style.display = 'flex'
card.style.flexDirection = 'row'
card.style.gap = '12px'
card.style.background = '#dbeafe'
return 'sm: горизонтальная раскладка (300px+)'
}
},
{
minWidth: 500,
apply() {
card.style.background = '#d1fae5'
return 'lg: широкая карточка (500px+)'
}
},
]
function applyContainerRules(element) {
const width = element.offsetWidth
let activeRule = null
for (const rule of containerRules) {
if (width >= rule.minWidth) {
activeRule = rule
}
}
if (activeRule) {
const desc = activeRule.apply()
info.textContent = `Ширина контейнера: ${width}px\nАктивное правило: ${desc}`
}
}
// Применяем при изменении размера
const ro = new ResizeObserver(() => applyContainerRules(container))
ro.observe(container)
applyContainerRules(container)
console.log('Попробуй растянуть контейнер — он имеет resize: horizontal')
console.log('Container queries работают именно так: ResizeObserver + условные стили')Создай адаптивную карточку с помощью Container Queries. Оберни карточку в контейнер с `container-type: inline-size`. При ширине контейнера меньше 350px карточка должна быть вертикальной (flex-direction: column), при 350px и больше — горизонтальной (flex-direction: row). Попробуй разместить одну и ту же карточку в узком и широком контейнерах.
`container-type: inline-size` — отслеживать ширину контейнера. `container-name: card` — имя для обращения в `@container`. В `@container card (min-width: 350px)` задай `flex-direction: row` для горизонтальной раскладки.