window.matchMedia()

Медиа-запросы в CSS — ключевой компонент адаптивного дизайна. Это отличный способ для применения разных стилей в зависимости от контекста, например, на основе размера области просмотра, предпочтительной цветовой схемы, конкретных взаимодействий и определенных устройств: принтеры, телевизоры, проекторы и т.д.

Знаете ли вы, что медиа-запросы есть для JavaScript? Наверное, не часто в JavaScript приходится встречаться с ними, однако есть очень полезные варианты использования их для реализации адаптивных плагинов: карусели, слайдеры. Например, когда при определенной ширине html-страницы требуется перерисовать и пересчитать элементы слайдера.

Работа с медиа-запросами в JavaScript отличается от работы с ними в CSS, хотя концепции схожи: сопоставить некоторые условия и применить какие-то вещи.

Использование matchMedia()

Чтобы определить, соответствует ли документ строке медиа-запроса в JavaScript, следует использовать метод matchMedia(). Несмотря на то, что он официально является частью спецификации модуля представления объектной модели CSS (CSS Object Model View Module specification), пока ещё находящейся в статусе черновика, его поддержка браузерами достигает 98,6%.

Использование почти идентично медиа-запросам в CSS. Надо передать строку медиа-запроса в метод matchMedia(), а затем проверить свойство .matches.


const mediaQuery = window.matchMedia('(min-width: 768px)');

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


// Условие для viewport шириной не менее 768 пикселей
const mediaQuery = window.matchMedia('(min-width: 768px)')

// Проверить, что media query будет true
if (mediaQuery.matches) {
  // Вызвать метод alert
  alert('Media Query Matched!')
}

Это базовое использование медиа-запросов в JavaScript. Добавляем условие (метод matchMedia()), которое возвращает объект (MediaQueryList), проверяем его (свойство .matches), а затем делаем что-то, если это условие возвращает true. Не сильно отличается от CSS!

Но это ещё не всё. Например, если изменить размер окна браузера, ничего не изменится, как это происходит в CSS. Это потому, что .matches реализует разовую проверку и не может отслеживать наличие изменений.

Отслеживание изменений

У MediaQueryList есть метод addListener() (а заодно и removeListener()), который принимает функцию-callback (для события .onchange). Она вызывается при изменении состояния медиа-запроса. Таким образом можно реагировать на изменения и многократно проверять условия медиа-запроса.


// Условие для viewport шириной не менее 768 пикселей
const mediaQuery = window.matchMedia('(min-width: 768px)');

function handleTabletChange(e) {
  // Проверить, что media query будет true
  if (e.matches) {
    // Вывести сообщение в консоль
    console.log('Media Query = ', e.matches);
  }
}

// Слушать события
mediaQuery.addListener(handleTabletChange);

// Начальная проверка
handleTabletChange(mediaQuery);

Устаревший способ сделать то же самое

Наиболее распространенный подход — наблюдать за событием resize, которое возникает при изменениях размеров окна и проверять window.innerWidth и/или window.innerHeight.


function checkMediaQuery() {
  // Если ширина окна > 768px
  if (window.innerWidth > 768) {
    // Вывести сообщение в консоль
    console.log('Media Query = true');
  }
}

// Наблюдаем за изменениями размеров окна
window.addEventListener('resize', checkMediaQuery);

Событие resize очень много раз вызывается при изменении размеров окна браузера, это — дорогостоящая операция.

resize vs matchMedia

Сравнение производительности matchMedia() и resize для пустой страницы.

resize vs matchMedia

В консоли это будет нагляднее, можно посмотреть и сравнить количество вызовов callback-функций для событий из matchMedia() и resize

Даже если не обращать внимания на проблемы с производительностью, отслеживание только изменений размеров окна (resize) сильно ограничено и не позволяет определять тип устройства (принтер, телевизор), ориентацию экрана. Таким образом, использование matchMedia() даёт больше возможностей в определении состояния html-страницы, чем информация только о его размерах при использовании resize.

Итоги

Вот так работают медиа-запросы в JavaScript. Мы изучили, как matchMedia() определяет условия окружения и рассмотрели объект MediaQueryList, который позволяет выполнять одноразовые (.matches) и регулярные (addListener()) проверки этих условий, чтобы реализовать возможность реагировать на их изменения (.onchange).

See this code matchMedia on x.xhtml.ru.

А также вспомнили «старый» способ отслеживать события изменения размеров html-документа в окне браузера. Хотя он по-прежнему широко используется и является вполне допустимым способом реакции на изменения размера window.innerWidth, он не может реализовать возможности для проверки расширенных условий аналогично медиа-запросам.