Метод 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 + change | resize + 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 проще проверять – расхождение мобильной вёрстки и мобильной логики находится за минуты, а не всплывает жалобами пользователей.