MediaQueryList — медіазапити з JavaScript і matchMedia


Об'єкт MediaQueryList у JavaScript

Об'єкт MediaQueryList у JavaScript є потужним інструментом, який дозволяє вам взаємодіяти з медіа-запитами безпосередньо зі скрипту. У сучасному веб-розробництві адаптивний дизайн є ключовою вимогою. Це означає, що ваш сайт або веб-додаток повинен коректно відображатися на різних пристроях з різними розмірами екрану та орієнтацією.

Створення та використання MediaQueryList

MediaQueryList створюється за допомогою метода window.matchMedia(). Цей метод приймає медіа-запит як аргумент і повертає об'єкт MediaQueryList.

Приклад:

let mql = window.matchMedia('(max-width: 600px)');
console.log(mql.matches);  // повертає true, якщо ширина екрану менша або дорівнює 600px, інакше - false

У цьому прикладі ми створюємо MediaQueryList для визначення, чи є ширина екрану користувача меншою або дорівнює 600 пікселям.

Події MediaQueryList та їх обробники

Однією з найцікавіших особливостей MediaQueryList є можливість відслідковувати зміни у результаті медіа-запиту. Це робиться за допомогою події change.

Приклад:

let mql = window.matchMedia('(max-width: 600px)');

function handleWidthChange(e) {
    if (e.matches) {
        console.log('Екран має ширину до 600 пікселів');
    } else {
        console.log('Екран більший за 600 пікселів');
    }
}

mql.addEventListener('change', handleWidthChange);

Використовуючи обробник подій, ми можемо динамічно реагувати на зміни у розмірах екрану, наприклад, змінювати стиль або структуру веб-сторінки відповідно до умов медіа-запиту.

автор: Bond

Властивості MediaQueryList

Об'єкт MediaQueryList включає в себе декілька ключових властивостей, які допомагають розробникам отримати доступ до інформації про медіа-запит та дізнатися, чи задовольняє поточний стан пристрою вказаним умовам.

matches:

  • Опис: Ця властивість вказує, чи задовольняє поточний стан пристрою умовам медіа-запиту.
  • Тип даних: Boolean (true або false).
  • Приклад:
let mql = window.matchMedia('(max-width: 600px)');
if (mql.matches) {
		console.log('Екран менше 600 пікселів у ширину');
} else {
		console.log('Екран 600 пікселів або більше у ширину');
}

В цьому прикладі, якщо ширина екрану користувача менше 600 пікселів, властивість matches поверне true, і в консолі буде виведено повідомлення про це. У протилежному випадку виведеться інше повідомлення.

media:

  • Опис: Властивість media дозволяє отримати рядкове представлення медіа-запиту, асоційованого з конкретним об'єктом MediaQueryList.
  • Тип даних: String.
  • Приклад:
let mql = window.matchMedia('(orientation: portrait)');
console.log(mql.media);  // Виведе: "(orientation: portrait)"

Тут ми створили об'єкт MediaQueryList для визначення орієнтації екрану (портретна). Властивість media дозволить нам перевірити, який саме медіа-запит було використано.

Ці властивості роблять MediaQueryList дуже корисним, коли вам потрібно робити умовні перевірки або відслідковувати зміни у відповідності до медіа-запитів, не використовуючи CSS.

автор: Bond

Загальний опис

автор: NagarD

Порада

Старайтеся формулювати медіа-запити якнайбільш конкретно, щоб уникнути небажаних перетинів. Наприклад, замість того, щоб використовувати (min-width: 600px)' та '(min-width: 900px), використовуйте (min-width: 600px) and (max-width: 899px).

автор: Bond
Порада

Коли ви використовуєте об'єкт MediaQueryList, не забудьте використовувати подію change, щоб відслідковувати зміни стосовно відповідності медіа-запиту. Це особливо корисно, коли вам потрібно динамічно адаптуватися до змін умов пристрою користувача.

автор: Bond
Порада

Коли ви використовуєте MediaQueryList для внесення динамічних змін у ваш інтерфейс, може бути корисно використовувати функції-обгортки або допоміжні бібліотеки для спрощення коду та зменшення його дублювання.

автор: Bond
Порада

Не всі браузери можуть підтримувати MediaQueryList або окремі види медіа-запитів. Перед використанням переконайтеся, що ви додали відповідні перевірки підтримки:

if (window.matchMedia) {
    let mql = window.matchMedia('(max-width: 600px)');
    // ... ваш код
}
автор: Bond

Синтаксис

let mediaQueryList = window.matchMedia(<string>);

Переглядачі

Підтримка: стаціонарні переглядачі
Переглядач Google Chrome Mozilla Firefox Apple Safari Opera Microsoft EDGE Microsoft Internet Explorer
9 6 5.1 12.1 12 10
Підтримка: мобільні переглядачі
Переглядач Mobile Google Android Mobile Google Chrome Mobile Mozilla Firefox Mobile Apple Safari
4.4 18 6 5

Приклади

+ запропонувати свій приклад у пісочниці

При невеликій ширині екрану навігаційна панель може стати "гамбургер"-меню, що зберігає місце.

HTML

<div class="navbar">перший елемент navbar</div>
<div class="navbar">другий елемент navbar</div>

JS

let mql = window.matchMedia('(max-width: 768px)');

function adaptNavbar(e) {
    const navbar = document.querySelector('.navbar');
    if (e.matches) {
        navbar.classList.add('hamburger-menu');
    } else {
        navbar.classList.remove('hamburger-menu');
    }
}

mql.addEventListener('change', adaptNavbar);
adaptNavbar(mql); // Для первісної установки стилю навігаційної панелі
автор: Bond

Прогін по всіх межах об’єкта: звідки він береться, як браузер нормалізує рядок запиту, чому хибний запит нічого не кидає, чому два виклики дають різні об’єкти — і як через це ламається зняття слухача. Результати йдуть у консоль і в таблицю на сторінці; поміняйте ширину вікна, щоб побачити подію change.

// ── таблиця для виводу: у пісочниці вікон і сповіщень немає, тож пишемо в DOM ──
document.head.append(Object.assign(document.createElement('style'), {
    textContent: 'table{border-collapse:collapse;font:14px/1.4 system-ui,sans-serif}'
        + 'th,td{padding:5px 10px;border:1px solid #b9c6d8;text-align:left;vertical-align:top}'
        + 'th{background:#eef4ff}td:first-child{color:#5a6472}'
}));
const table = document.createElement('table');
table.innerHTML = '<thead><tr><th>що питаємо</th><th>що вийшло</th></tr></thead><tbody></tbody>';
document.body.append(table);
const tbody = table.querySelector('tbody');
const show = (pytannia, vidpovid) => {
    const row = tbody.insertRow();
    row.insertCell().textContent = pytannia;
    row.insertCell().textContent = String(vidpovid);
    console.log(pytannia + ' → ' + vidpovid);
};

// ── звідки береться об'єкт ──
const mq = window.matchMedia('(max-width: 600px)');
show('typeof matchMedia(...)', mq.constructor.name);       // → MediaQueryList
show('це EventTarget?', mq instanceof EventTarget);        // → true
try {
    new MediaQueryList();
} catch (e) {
    show('new MediaQueryList()', e.name + ': ' + e.message); // → TypeError: Illegal constructor.
}

// ── два поля, заради яких усе й затівалося ──
show('mq.media', mq.media);                                 // → (max-width: 600px)
show('mq.matches (ширина рамки зараз)', mq.matches);        // → true або false, залежно від рамки

// ⚠️ matches тільки для читання: присвоєння мовчки нічого не робить
const bulo = mq.matches;
mq.matches = !bulo;
show('після mq.matches = !mq.matches', mq.matches === bulo ? 'не змінилося' : 'змінилося'); // → не змінилося

// ── браузер нормалізує рядок запиту ──
show('"  (MAX-WIDTH: 600PX)  "', window.matchMedia('  (MAX-WIDTH: 600PX)  ').media); // → (max-width: 600px)
show('незакрита дужка "(width: 1px"', window.matchMedia('(width: 1px').media);       // → (width: 1px)

// ⚠️ хибний запит НЕ кидає помилки — він стає "not all" і не збігається ніколи
const bad = window.matchMedia('це не медіа-запит');
show('хибний запит → media', bad.media);       // → not all
show('хибний запит → matches', bad.matches);   // → false

// ⚠️ порожній рядок — це "будь-який носій", тобто ЗАВЖДИ істина
const empty = window.matchMedia('');
show('порожній запит → media', JSON.stringify(empty.media)); // → ""
show('порожній запит → matches', empty.matches);             // → true

// ⚠️ кожен виклик віддає НОВИЙ об'єкт — і саме на цьому ламається зняття слухача
const a = window.matchMedia('(min-width: 1px)');
const b = window.matchMedia('(min-width: 1px)');
show('два виклики з тим самим рядком: a === b', a === b);   // → false
show('але значення в них однакове', a.matches === b.matches); // → true

// ── корисні запити, які взагалі не про ширину ──
for (const zapyt of ['(prefers-color-scheme: dark)', '(prefers-reduced-motion: reduce)',
    '(pointer: coarse)', '(hover: hover)', '(orientation: portrait)', 'print']) {
    show(zapyt, window.matchMedia(zapyt).matches);
}

// ── подія change: тримаємо ОДИН об'єкт у змінній, інакше слухача не зняти ──
const vuzka = window.matchMedia('(max-width: 500px)');
vuzka.addEventListener('change', (e) => {
    // у події свої поля matches і media — зовнішню змінну чіпати не треба
    show('подія change, ' + e.media, e.matches ? 'умова виконалася' : 'умова перестала виконуватися');
    console.log('тип події:', e.constructor.name); // → MediaQueryListEvent
});
show('слухач "(max-width: 500px)" почепився', 'поміняйте ширину вікна — рядок з’явиться нижче');
автор: NagarD

Дозволяє автоматично перемикати тему дизайну сторінки між світлою та темною в залежності від системних установок користувача.

let mql = window.matchMedia('(prefers-color-scheme: dark)');

function toggleTheme(e) {
    if (e.matches) {
        document.body.classList.add('dark-theme');
    } else {
        document.body.classList.remove('dark-theme');
    }
}

mql.addEventListener('change', toggleTheme);
toggleTheme(mql); // Для первісної установки теми
автор: Bond

Якщо користувач використовує режим високого контрасту для покращення читабельності, виведіть спеціальне повідомлення, щоб відзначити це.

let mql = window.matchMedia('(forced-colors: active)');

function notifyHighContrast(e) {
    if (e.matches) {
        alert('Ви використовуєте режим високого контрасту. Дизайн сайту може відрізнятися.');
    }
}

mql.addEventListener('change', notifyHighContrast);
notifyHighContrast(mql); // Для первісного виявлення установок
автор: Bond

Коментарі

Коментарів ще немає — будьте першим!

Відповідь для

Як форматувати текст?
Пишемо Виходить
**важливо**
*акцент*
~~застаріле~~
Виділення в тексті. Те саме роблять кнопки панелі, що спливає над виділеним.
## Як працює каскад
### Порядок оголошень
Заголовки всередині тексту. Найбільший не потрібен — він у сторінки вже є.
[[css/property/color]] Посилання на сторінку довідника. Наберіть [[ — поле веде сходинками (розділ → тип → сторінка), а хибний ключ підкреслить хвилею.
[[!sandbox/S0oh1hBm]] Живий приклад окремим рядком — саме вікно результату. Той самий ключ зі знаком !: наберіть [[! і дійдіть сходинками до потрібної сторінки, список покаже її приклади.
[MDN](https://developer.mozilla.org) Посилання на чужий сайт.
![Схема каскаду](https://site.com/схема.png) Картинка. Опис обов'язковий: його читають пошуковик і незрячі.
Задайте `display: grid` контейнеру Код усередині речення.
```css
.card { display: grid }
```
Код блоком. Мова після трьох лапок вмикає підсвітку.
- Походження
1. Походження
Перелік: маркований і нумерований.
- [ ] дописати приклади
- [x] перевірити в Firefox
Перелік завдань із галочками. Пробіл у порожніх дужках обов'язковий.
> Порядок важить лише за рівної ваги. Цитата на всю ширину абзацу.
Специфічність
: вага селектора; більша перемагає
Список означень: термін, а під ним пояснення з відступом. Пробіл після двокрапки обов'язковий.
Каскад має три критерії[^1]
[^1]: Походження, специфічність, порядок.
Виноска: номер у тексті, примітка внизу сторінки. Без згадки [^1] у тексті примітка не з'явиться.
Відповідь: ||сорок два|| Ховає те, що між рисками: читач бачить на цьому місці розмиту пляму, а решта речення лишається звичайною. Клац — і текст відкрито.
|| Показати рішення
Задайте контейнеру `display: grid`.
||
Ховає цілі абзаци. На сторінці видно лише підпис із першого рядка («Показати рішення»), решта розгортається клацанням по ньому. Закривні риски — окремим рядком.
Порожній рядок Новий абзац. Щоб перенести рядок усередині абзацу, поставте \ в його кінці.

Увійдіть — коментар з'явиться одразу, без капчі й очікування перевірки.

Правка
Зворотний зв'язок
Вхід