Media queries, container queries, viewport units и prefers-*
Адаптивность изначально строилась вокруг viewport: ширина экрана определяла, как выглядит интерфейс. Но в компонентной эре этого недостаточно: один и тот же компонент может жить и в узком сайдбаре, и в широком основном контенте. Container queries решают эту задачу, смещая фокус с экрана на размер самого компонента. Вместе с viewport units и prefers-*-запросами они формируют современный инструментарий адаптивного CSS.
Содержание
- Глубокий разбор
- Практические примеры
- Типичные ошибки и антипаттерны
- Ключевые тезисы для интервью
- Заключение
- Полезные ссылки
Глубокий разбор
Media queries
Media queries (медиа-запросы) позволяют применять стили в зависимости от характеристик устройства, браузера или предпочтений пользователя.
@media (min-width: 768px) {
.layout {
display: grid;
grid-template-columns: 240px 1fr;
}
}
Основные media features:
width,min-width,max-width— ширина viewport.height,min-height,max-height— высота viewport.aspect-ratio— соотношение сторон viewport.orientation—portraitилиlandscape.hover— есть ли устройство, поддерживающее наведение (hover: hover/hover: none).pointer— точность указателя (coarseдля тачскрина,fineдля мыши/стилуса).prefers-*— пользовательские предпочтения (о них ниже).
Медиа-запросы логично комбинировать:
@media (min-width: 768px) and (max-width: 1199px) {
/* только планшеты */
}
Container queries
Container queries (контейнерные запросы) применяют стили на основе размеров не viewport, а конкретного контейнера. Это позволяет компоненту адаптироваться под пространство, которое ему выделено.
Чтобы использовать container queries, нужно сначала объявить контейнер:
.card-list {
container-type: inline-size;
}
container-type может принимать значения:
size— запросы могут проверять и ширину, и высоту контейнера.inline-size— запросы могут проверять только inline-размер (ширину в горизонтальном письме).normal— контейнер не создаётся.
После этого можно писать запросы:
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
}
}
Контейнеры можно именовать:
.card-list {
container-type: inline-size;
container-name: cards;
}
@container cards (min-width: 400px) {
.card { ... }
}
Viewport units
Viewport units (единицы viewport) выражают размер относительно viewport.
vw— 1% ширины viewport.vh— 1% высоты viewport.vmin— меньшее изvwиvh.vmax— большее изvwиvh.
.hero {
min-height: 100vh;
}
В мобильных браузерах появились учитывающие интерфейс браузера единицы:
svh/svw— small viewport (минимальные размеры без учёта скрывающихся панелей).lvh/lvw— large viewport (максимальные размеры).dvh/dvw— dynamic viewport (текущие размеры с учётом панелей).
.hero {
min-height: 100dvh; /* адаптируется под появление/скрытие панелей */
}
Также существуют единицы относительно контейнера:
cqw— 1% ширины контейнера.cqh— 1% высоты контейнера.cqi— 1% inline-размера контейнера.cqb— 1% block-размера контейнера.
prefers-* и доступность
Группа media features prefers-* позволяет адаптировать интерфейс под пользовательские системные настройки.
prefers-color-scheme
@media (prefers-color-scheme: dark) {
:root {
--bg: #111827;
--text: #f9fafb;
}
}
prefers-reduced-motion
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Важно: жёсткий сброс анимаций может ломать компоненты, которые используют transition для состояний. Лучше сбрасывать конкретные анимации, а не все подряд.
prefers-contrast
@media (prefers-contrast: more) {
.button {
border: 2px solid currentColor;
}
}
prefers-reduced-transparency
@media (prefers-reduced-transparency: reduce) {
.glass {
background: rgba(255, 255, 255, 0.95);
}
}
Практические примеры
Пример 1: карточка, адаптирующаяся под ширину родителя
<div class="card-list">
<article class="card">
<img src="thumb.jpg" alt="">
<div class="content">
<h3>Title</h3>
<p>Description</p>
</div>
</article>
</div>
.card-list {
container-type: inline-size;
container-name: cards;
}
.card {
display: flex;
flex-direction: column;
gap: 16px;
}
@container cards (min-width: 400px) {
.card {
flex-direction: row;
}
.card img {
width: 120px;
flex-shrink: 0;
}
}
Если .card-list шире 400px, карточка перестраивается в горизонтальный вид. Если этот же компонент вставить в узкий сайдбар, он останется вертикальным.
Пример 2: комбинация media и container queries
.page {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 1024px) {
.page {
grid-template-columns: 280px 1fr;
}
}
.widget {
container-type: inline-size;
}
@container (min-width: 500px) {
.widget {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
Media query управляет глобальной раскладкой страницы, а container query — поведением виджета внутри выделенного ему пространства.
Пример 3: viewport units с динамической высотой
.fullscreen {
min-height: 100dvh;
display: grid;
place-items: center;
}
100dvh лучше подходит для мобильных устройств, где адресная панель может появляться и скрываться. 100vh на iOS может давать лишний скролл или обрезку контента.
Пример 4: адаптация под предпочтения пользователя
:root {
--bg: #ffffff;
--text: #111827;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111827;
--text: #f9fafb;
}
}
@media (prefers-contrast: more) {
:root {
--text: #000000;
}
}
@media (prefers-reduced-motion: reduce) {
.spinner {
animation: none;
}
}
Пример 5: container query units для типографики
.banner {
container-type: inline-size;
}
.banner-title {
font-size: clamp(1.25rem, 5cqi, 3rem);
}
Размер заголовка масштабируется относительно ширины баннера, а не viewport. Это удобно для переиспользуемых компонентов.
Типичные ошибки и антипаттерны
- Использование viewport-запросов там, где нужны container queries. Компонент не должен знать, на каком экране он находится; он должен реагировать на свой размер.
- Забытое
container-typeу родителя. Без него@containerне найдёт контекста и не применится. - Использование
100vhна мобильных без учёта панелей. Для полноэкранных секций предпочтительнее100dvh/100svh. - Переопределение всех анимаций в
prefers-reduced-motion. Грубый сброс ломает микровзаимодействия. Отключайте именно те анимации, которые не несут смысловой нагрузки. - Игнорирование
prefers-contrastиprefers-reduced-transparency. Эти настройки важны для пользователей с нарушениями зрения. - Путаница
min-widthиmax-widthв media queries. Mobile-first подход предполагает базовые стили для маленьких экранов и@media (min-width: ...)для больших. - Слишком много breakpoint’ов. Если в проекте десятки breakpoints, возможно, стоит перейти к container queries или использовать относительные единицы.
- Неучёт логических осей в container queries. В вертикальном письме
inline-size— это высота, а не ширина.cqiиcqbпомогают писать более универсальный код.
Ключевые тезисы для интервью
- Media queries реагируют на характеристики viewport и устройства:
width,height,orientation,hover,pointer,prefers-*. Container queries реагируют на размер контейнера, а не экрана — лучше для компонентов в разных частях layout’а. - Container queries требуют
container-typeу родителя:inline-sizeпозволяет запрашивать ширину,size— и ширину, и высоту. Контейнеры можно именовать черезcontainer-nameи использовать в@container name (условие). - Viewport units:
vw/vh,vmin/vmax;svh/lvh/dvhрешают проблему мобильных браузеров, где100vhне совпадает с видимой областью из-за адресной строки. - Container query units:
cqw/cqh/cqi/cqbвыражают размер относительно контейнера, позволяя адаптировать типографику и отступы без media queries. prefers-color-schemeуправляет тёмной/светлой темой;prefers-reduced-motionуменьшает анимации;prefers-contrastиprefers-reduced-transparencyулучшают accessibility.prefers-reduced-motionстоит обрабатывать избирательно: отключать конкретные анимации (transition, animation, transform), а не все эффекты глобально — некоторые анимации важны для UX.
Заключение
Адаптивный CSS эволюционировал от «ширина экрана определяет всё» к компонентно-ориентированной модели. Container queries позволяют компоненту реагировать на своё доступное пространство, а не на размер экрана. Viewport units нового поколения (dvh, svh) решают давнюю проблему с мобильными браузерами. prefers-*-запросы позволяют уважать системные настройки пользователя: тему, доступность и анимации. Современная практика — использовать media queries для глобального макета страницы и container queries для адаптации компонентов.