На главную

CSS и вёрстка

Медиазапросы и контейнерные

Точки перелома в готовые @media: mobile-first или наоборот, px или em, новый синтаксис диапазонов. Плюс запросы к возможностям устройства, миксины SCSS, matchMedia и контейнерные запросы.

Точки перелома

По одной в строке: «md: 768». Только возрастание

Запросы к возможностям устройства

CSS

/* Сначала телефон, потом добавляем. Базовые правила живут вне запросов. */

@media (min-width: 640px) {
  /* sm — от 640px */
  .container { }
}

@media (min-width: 768px) {
  /* md — от 768px */
  .container { }
}

@media (min-width: 1024px) {
  /* lg — от 1024px */
  .container { }
}

@media (min-width: 1280px) {
  /* xl — от 1280px */
  .container { }
}

@media (min-width: 1536px) {
  /* 2xl — от 1536px */
  .container { }
}

/* Системная тема пользователя. Ручной переключатель на сайте должен уметь перебить её. */
@media (prefers-color-scheme: dark) {
  .container { }
}

/* Человеку от анимации становится плохо. Не «выключить красоту», а убрать длинные перемещения. */
@media (prefers-reduced-motion: reduce) {
  .container { }
}

Миксины SCSS

@use 'sass:math';

$breakpoints: (
  'sm': 640px,
  'md': 768px,
  'lg': 1024px,
  'xl': 1280px,
  '2xl': 1536px,
);

@mixin at($name) {
  $px: map-get($breakpoints, $name);
  @if not $px {
    @error 'Нет точки перелома «#{$name}»';
  }
  $value: $px;
  @media (min-width: #{$value}) {
    @content;
  }
}

/* .card { padding: 8px; @include at('md') { padding: 24px; } } */

matchMedia в JS

const QUERIES = {
  sm: '(min-width: 640px)',
  md: '(min-width: 768px)',
  lg: '(min-width: 1024px)',
  xl: '(min-width: 1280px)',
  '2xl': '(min-width: 1536px)',
};

// Браузер сам зовёт обработчик, когда условие перестаёт или начинает выполняться.
// Слушать изменение размера окна ради этого не нужно: лишние срабатывания на каждый пиксель.
export function watch(name, onChange) {
  const mq = window.matchMedia(QUERIES[name]);
  onChange(mq.matches);
  const handler = (event) => onChange(event.matches);
  mq.addEventListener('change', handler);
  return () => mq.removeEventListener('change', handler);
}

Контейнерные запросы

Ширина окна ничего не говорит о месте под карточку: в сайдбаре она узкая и на широком мониторе. Контейнерный запрос спрашивает про блок, а не про экран, — одна карточка работает и в ленте, и в колонке.

container.css

/* Контейнером объявляется родитель, а не сам блок: элемент не может
   отвечать на собственную ширину — он же её и задаёт. */
.card-wrap {
  container-type: inline-size;
  container-name: card;
}

@container card (min-width: 400px) {
  /* wide: место под карточку шире 400px — неважно, какой ширины окно */
  .card { }
}

@container card (min-width: 640px) {
  /* full: место под карточку шире 640px — неважно, какой ширины окно */
  .card { }
}

/* Внутри контейнера работают свои единицы: 1cqi — сотая доля его ширины. */
.card__title {
  font-size: clamp(1rem, 4cqi, 1.75rem);
}