Навигация

Media queries, container queries, viewport units и prefers-*

Адаптивность изначально строилась вокруг viewport: ширина экрана определяла, как выглядит интерфейс. Но в компонентной эре этого недостаточно: один и тот же компонент может жить и в узком сайдбаре, и в широком основном контенте. Container queries решают эту задачу, смещая фокус с экрана на размер самого компонента. Вместе с viewport units и prefers-*-запросами они формируют современный инструментарий адаптивного CSS.

Содержание

  1. Глубокий разбор
  2. Практические примеры
  3. Типичные ошибки и антипаттерны
  4. Ключевые тезисы для интервью
  5. Заключение
  6. Полезные ссылки

Глубокий разбор

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 для адаптации компонентов.

Полезные ссылки