window.matchMedia()

window.matchMedia() – метод JavaScript, который проверяет, соответствует ли документ CSS-медиазапросу. Возвращает MediaQueryList со свойством matches и событием change для слежения за брейкпоинтами и темой ОС.

3 минуты чтения

Метод window.matchMedia() – мост между CSS-медиазапросами и JavaScript: те же условия, что в правиле @media – «(max-width: 600px)» или «(prefers-color-scheme: dark)», – можно разово проверить и отслеживать из скрипта. Метод описан в спецификации CSSOM View и работает во всех браузерах с июля 2015 года (в Internet Explorer – с версии 10), так что полифилы не нужны. На практике matchMedia() закрывает три типовые задачи: переключение JS-логики на брейкпоинтах, определение тёмной темы пользователя и уважение к настройке уменьшенного движения – последнее уже вопрос доступности.

Как это работает

Вызов window.matchMedia("(max-width: 600px)") возвращает объект MediaQueryList с двумя свойствами: matches – булево значение, соответствует ли документ запросу в момент чтения, и media – сериализованная строка самого запроса. Синтаксис строки – ровно как в CSS: media-функции обязаны стоять в скобках ("(max-width: 600px)" – верно, "max-width: 600px" – ошибка), а типы (screen, print, all) и операторы and, or, not, only пишутся без скобок.

Для слежения за изменениями MediaQueryList генерирует событие change. Оно срабатывает при пересечении границы условия в любую сторону: окно сузилось ниже брейкпоинта – событие, расширилось обратно – снова событие. Обработчик получает объект MediaQueryListEvent с теми же свойствами matches и media. Важный нюанс: при загрузке страницы change не срабатывает – начальное состояние проверяют через свойство matches и при необходимости вызывают обработчик один раз вручную.

  • addEventListener("change", handler) – современный способ подписки: MediaQueryList наследует EventTarget, событие доступно во всех браузерах (Baseline) с сентября 2020 года.
  • addListener() / removeListener() – устаревшие (deprecated) методы. Единственная причина, по которой они задерживались в коде – Safari до версии 14, где MediaQueryList ещё не наследовал EventTarget. В новом коде им делать нечего.

matchMedia и событие resize – в чём разница

До matchMedia адаптивную JS-логику строили на слушателе resize: на каждое изменение размера окна скрипт читал innerWidth и сравнивал с числом. Разница принципиальная:

Сравнение matchMedia и слушателя resize
КритерийmatchMedia + changeresize + innerWidth
Частота вызововТолько при пересечении брейкпоинтаНа каждый пиксель изменения окна
УсловияТе же строки, что в CSS @mediaЧисла, продублированные в JS
Синхронность с вёрсткойБрейкпоинты гарантированно совпадают с CSSЛегко разъезжаются при правке стилей
ВозможностиЛюбые media-функции: тема, ориентация, движениеТолько размеры окна

Зачем это нужно

JS-брейкпоинты. Тяжёлую функциональность включают только там, где она нужна: слайдер или карта инициализируются на десктопе, мобильное меню – на узких экранах. Обработчики вешаются и снимаются в момент пересечения брейкпоинта, а не проверяются на каждое движение окна – это экономит ресурсы, что особенно заметно на мобильных устройствах. Если интерактивных элементов много, обработчики удобно вешать через делегирование событий – тогда при смене режима достаточно заменить один слушатель на контейнере.

Тёмная тема. Запрос window.matchMedia("(prefers-color-scheme: dark)") сообщает, что пользователь выбрал тёмную тему в настройках операционной системы, а подписка на change ловит переключение на лету – например, при автоматической смене темы вечером. Результат обычно записывают в класс или data-атрибут на корневом элементе, и дальше работает CSS.

Уменьшенное движение. Запрос window.matchMedia("(prefers-reduced-motion: reduce)") – тот же паттерн для пользователей, которым анимации мешают или физически вредят. CSS-анимации отключает медиазапрос в стилях, а matchMedia закрывает JS-часть: автопрокрутку, параллакс, декоративные эффекты, запущенные скриптами.

Правило выбора простое: если задача решается чистым CSS-медиазапросом, JavaScript не нужен вообще. matchMedia берут тогда, когда на брейкпоинте меняется поведение, а не только внешний вид: другая логика событий, другие обработчики, другие данные.

Пример

Слежение за мобильным брейкпоинтом с корректной обработкой начального состояния:

const mql = window.matchMedia("(max-width: 600px)");

function onBreakpoint(e) {
  if (e.matches) {
    initMobileMenu();
  } else {
    destroyMobileMenu();
  }
}

mql.addEventListener("change", onBreakpoint);
onBreakpoint(mql); // вызвать вручную: change при загрузке не сработает

Обработчик принимает и MediaQueryListEvent, и сам MediaQueryList – у обоих есть свойство matches, поэтому одна функция обслуживает и загрузку страницы, и все последующие изменения.

Определение тёмной темы с реакцией на смену настроек ОС:

const darkMode = window.matchMedia("(prefers-color-scheme: dark)");

document.documentElement.classList.toggle("dark", darkMode.matches);

darkMode.addEventListener("change", (e) => {
  document.documentElement.classList.toggle("dark", e.matches);
});

На Битрикс-проектах я использую этот паттерн, когда мобильная и десктопная версии различаются поведением: например, галерея, которая на широком экране работает как сетка, а на мобильном превращается в слайдер. Инициализация и уничтожение слайдера привязаны к событию change – скрипт не дёргается впустую на каждом resize, а условия брейкпоинтов совпадают с CSS-файлом один в один. Отдельный плюс для аудита: единые условия в CSS и JS проще проверять – расхождение мобильной вёрстки и мобильной логики находится за минуты, а не всплывает жалобами пользователей.

Частые вопросы

Что возвращает window.matchMedia()?

Объект MediaQueryList. У него два свойства: matches – булево значение, соответствует ли документ медиазапросу прямо сейчас, и media – сериализованная строка самого запроса. Этот же объект генерирует событие change при изменении результата запроса.

Как следить за изменением медиазапроса в JavaScript?

Подписаться на событие change: mql.addEventListener('change', handler). Обработчик получает объект MediaQueryListEvent со свойством matches. При загрузке страницы change не срабатывает, поэтому для начального состояния обработчик вызывают один раз вручную сразу после подписки.

Чем matchMedia лучше слушателя resize?

Событие change срабатывает только при пересечении границы условия, а resize – на каждый пиксель изменения окна, то есть сотни раз за одно перетаскивание. Условия matchMedia совпадают с CSS-брейкпоинтами буквально, а не продублированы числами в JS, и покрывают любые media-функции: тему, ориентацию, уменьшенное движение.

Можно ли ещё использовать addListener() и removeListener()?

Они официально устарели (deprecated). Единственная причина, по которой они задерживались в коде – Safari до версии 14, где MediaQueryList не наследовал EventTarget. В новом коде используют addEventListener('change') и removeEventListener – событие доступно во всех браузерах (Baseline) с сентября 2020 года.

Как определить тёмную тему пользователя из JavaScript?

Разовая проверка: window.matchMedia('(prefers-color-scheme: dark)').matches вернёт true, если в операционной системе выбрана тёмная тема. Чтобы реагировать на переключение темы на лету, подписываются на событие change этого же объекта MediaQueryList.

Почему matchMedia('max-width: 600px') не работает?

Синтаксис строки запроса – как в CSS: media-функции обязаны стоять в скобках. Правильно: matchMedia('(max-width: 600px)'). Без скобок строка не является корректным медиазапросом. Типы (screen, print, all) и операторы and, or, not, only при этом пишутся без скобок.

Срабатывает ли событие change при загрузке страницы?

Нет. Событие генерируется только при изменении результата запроса – например, когда окно пересекло брейкпоинт или пользователь сменил тему ОС. Начальное состояние проверяют через свойство matches и при необходимости вызывают обработчик вручную один раз.

Материалы по теме

Валентина Меланина

Нужна консультация?

Разберу ваш сайт и покажу точки роста

Если хотите понять, как этот термин применить к вашему проекту — начнём с аудита.