matchMedia

Przy pracy z różnymi urządzeniami bardzo często chcemy sprawdzać rozdzielczość urządzenia.

Jednym ze sposobów reakcji na zmianę rozmiaru ekranu przeglądarki, jest podpięcie się pod event resize obiektu window:


window.addEventListener('resize', function() {
    console.log(this);
});

Jest to bardzo często wykorzystywana metoda, która sprawdza się przy bardzo wielu sytuacjach. Dla przykładu chcemy zareagować, gdy ekran urządzenia jest mniejszy niż 768px:


window.addEventListener('resize', function() {
    if (this.innerWidth >= 768) {
        console.log("Wersja dekstopowa");
    } else {
        console.log("Wersja mobilna");
    }
});

Z takim podpinaniem się pod event resize wiążą się pewne problemy. Pierwszym z nich jest to, że jak zauważysz w konsoli podczas zmiany rozmiarów okna, event taki jest odpalany bardzo często. Przy prostych czynnościach jak przełączenie klasy nie powinno to być większym problemem. Bardzo często jednak przy takich zmianach ekranu będziemy chcieli robić bardziej skomplikowane czynności - np zmieniać skomplikowany slider w wersję "light".

Drugim problemem jest to, że media qurery w CSS query możemy testować nie tylko rozdzielczość ekranu, ale też gęstość pikseli, typ mediów, orientację urządzenia i podobne rzeczy. W powyższej metodzie jesteśmy skazani tylko na rozmiar ekranu.

Sprawdzanie elementu

Jeden ze sposobów rozwiązania tego problemu polega na tym, by zamiast sprawdzać rozdzielczość ekranu, wystarczy sprawdzić jakąś właściwość elementu testowego. Element taki można stworzyć za pomocą JS lub po prostu umieścić w html, a następnie wynieść poza ekran - tak by nie przeszkadzał użytkownikowi:


.test-element {
    position:absolute;
    left:-99999px;
    display:none;
}

@media only screen and (min-width:768px) {
    .test-element {
        display:block;
    }
}

const testElement = document.creatElement('span');
testElement.classList.add('test-element');

const style = window.getComputedStyle(testElement, null);

window.addEventListener('resize', function() {
    if (style.getPropertyValue('display') === 'block') {
        console.log("Wersja desktopowa");
    } else {
        console.log("Wersja mobilna");
    }
});

Za pomocą właściwości element.style możemy pobierać i ustawiać style inline danego elementu.

Jeżeli element ma ustawione stylowanie w arkuszach stylów (nie inline), właściwość ta nie zadziała. Aby pobrać takie stylowanie musimy skorzystać z metody window.getComputedStyle(element, pseudoEl*)

Problemem który pozostał to częstotliwość odpalania takiego eventu. Istnieją różne sposoby na obejście tego problemu. Można tutaj wykorzystać setTimeout dla spowolnienia nasłuchu, czy skorzystać z requestAnimationFrame. Odpowiednie przykłady znajdziesz na stronie: https://developer.mozilla.org/en-US/docs/Web/Events/resize

matchMedia

Inną metodą wykrywania mediów jest skorzystanie ze specjalnie przeznaczonego do tego celu obiektu window.matchMedia(mediaQuery)


if (window.matchMedia("(min-width: 768px)").matches) {
    console.log('Wersja dekstopowa');
} else {
    console.log('Wersja mobilna');
}

Zaletą tego rozwiązania jest to, że podane media mogą być tak samo precyzyjne jak te z CSS. Nie musimy się więc ograniczać do rozmiaru okna, ale też możemy sprawdzić inne media.

Powyższy kod zostanie odpalony jeden raz - tuż po wywołaniu (najczęściej przy wejściu na stronę).

Żeby powyższe spradzanie reagowało na zmianę mediów (np. rozmiaru okna), musimy podpiąć do niego listener:


const media = window.matchMedia("(min-width: 768px)");
if (media.matches) {
    console.log('Wersja desktopowa');
} else {
    console.log('Wersja mobilna');
}

media.addListener(function(media) {
    if (media.matches) {
        console.log('Wersja desktopowa');
    } else {
        console.log('Wersja mobilna');
    }
});

W przeciwieństwie do eventu resize który jest odpalany non stop przy zmianie rozmiaru okna, listener match media będzie odpalany tylko w momencie wykonania warunku z media.


function setBg(media) {
    if (media.matches) {
        body.style.setProperty('background-color', 'red');
    } else {
        body.style.setProperty('background-color', 'blue');
    }
}

const media = window.matchMedia("(min-width: 768px)");
const body = document.querySelector('body');

setBg(media);

media.addListener(setBg);

W razie potrzeby w każdej chwili możemy odpiąć nasłuchiwanie poprzez metodę removeListener(functionName):


media.removeListener(setBg);