W tym wpisie poruszymy prostą, acz bardzo przydatną metodę dostępną od jakiegoś czasu w Obiektowym Modelu Dokumentu, która pozwoli nam lepiej kontrolować wygląd i zachowanie naszych stron i aplikacji internetowych, które dostosowujemy do urządzeń mobilnych.

Mowa o metodzie matchMedia obiektu window.

Jak działa matchMedia?

Jeśli wiesz czym są CSS Media Queries, to szybko zrozumiesz, z czym mamy teraz do czynienia. Otóż wyobraź sobie, że możemy z poziomu kodu JavaScript przekazać przeglądarce dowolne media query i zapytać czy w danym momencie spełnia ono warunek. Media query może wyglądać następująco:

Wiemy, że przy takim zapisie w CSS, style zostaną zaaplikowane jedynie wówczas, gdy mamy do czynienia z urządzeniem posiadającym ekran i aktualną szerokością okna nie większą niż 480px.

Przekażmy zatem powyższy zapis do metody matchMedia. Metodę tę wywołujemy z poziomu obiektu window w ten sposób:

Do zmiennej mobileViewport zwrócona zostanie referencja do obiektu MediaQueryList (polecam podejrzeć w konsoli przeglądarki).

Teraz możemy zapytać, czy aktualna szerokość okna spełnia nasze warunki w ten sposób:

Ok, ale gdzie tu dynamika?

No właśnie. W powyższym przykładzie sprawdzamy, czy okno przeglądarki ma odpowiednią szerokość. Oczywiście do media query możemy dodać inne warunki znane z CSS, np. orientację ekranu itd. Problem jest jednak taki, że powyższe sprawdzenie wykonywane jest jednorazowo, a my otrzymujemy z .matches true lub false. Naszym celem będzie jednak wykonanie określonej funkcji na przykład wówczas, gdy zmieni się szerokość okna przeglądarki.

.addListener na ratunek

Dobra wiadomość jest taka, że do MediaQueryList możemy przypisać event listener w taki sposób

Funkcja zwrotna będzie wywoływana za każdym razem, gdy zmieni się warunek. W naszym przypadku, kiedy będziemy skalowali okno przeglądarki i zmniejszymy jego szerokość do 480px lub mniej, to jednokrotnie zostanie wywołana powyższa funkcja zwrotna. Gdy ponownie “wyskoczymy” powyżej wartości 480px, funkcja znów zostanie odpalona.

Z tego powodu w powyższej funkcji sprawdzamy za każdym razem, czy warunek media query jest spełniony. Jak wcześniej, odwołujemy się do mq.matches i dzięki temu możemy odpowiednio zmienić coś na stronie, np. opcje slidera, które będą inne dla wersji desktopowej itd.

Ważna uwaga jest jednak taka, że jeśli chcemy zaraz po wczytaniu się strony wywołać wspomnianą funkcję sprawdzającą, to musimy ją zapisać w zmiennej i wywołać “ręcznie”, a następnie jak wyżej przypisać do .addListener, gdyż event ten wywoływany będzie dopiero później (w naszym przypadku przy skalowaniu okna).

Oprócz .addListener, mamy również metodę .removeListener, która usunie funkcję zwrotną.

Dlaczego korzystać z .matchMedia?

Być może niejednokrotnie napisałeś kod jQuery tego typu:

Powyższe rozwiązanie nie jest dobrą praktyką, gdyż podczas skalowania okna przeglądarki bardzo wiele razy odpalimy funkcję zwrotną, która następnie zamknie obiekt window w obiekcie jQuery, a dopiero później sprawdzi szerokość skalowanego właśnie okna przeglądarki.

Dużo prościej wykonać tą samą czynność z .matchMedia, gdyż funkcja zwrotna zostanie wywołana jedynie raz, gdy warunek zostanie spełniony i drugi raz, gdy “wyskoczymy” poza 480px.

Dodatkowo oczywiście sprawdzić możemy orientację urządzenia np. poprzez orientation: landscape itd. Wówczas, gdy użytkownik przekręci telefon, media query spełni warunek, a nasza funkcja również zostanie wywołana.

Osobiście korzystam z takiego rozwiązania np. wtedy, gdy na stronie desktopowej umieszczam slider lub karuzelę. Podczas skalowania okna przeglądarki pojawia się potrzeba zmiany pewnych ustawień wspomnianych pluginów i to jest dobra metoda, by tego dokonać.

Wsparcie

Dobre wiadomości! Metoda .matchMedia jest dobrze wspierana zarówno w przeglądarkach desktopowych jak i mobilnych. Wsparcie możesz zobaczyć tutaj.

  • Niestety nie jest wspierana na starych androidach :/

    • To żaden problem, gdyż możemy skorzystać z licznych Polyfilów ;)

  • Przy korzystaniu z „czystego JavaScriptu” może być to bardzo przydatne, natomiast jeżeli używamy kodu jQuery opisanego powyżej (if($(this).width() <= 480)) wystarczy wówczas dodać zmienną, która będzie posiadała wartości true/false i też będzie ok. Będzie wtedy: if($(this).width() <= 480 && ekranPowyzej480px). Oczywiście tworzymy wcześniej zmienną: var ekranPowyzej480px = false lub true

    • Niestety nie w tym jest problem czy używamy „czystego” JavaScriptu czy jQuery. Problem jest w tym, że zdarzenie resize jest wywoływane bardzo wiele razy i nawet proste pobranie w nim aktualnej szerokości okna jest dla przeglądarki „męczące” kiedy musi to zrobić kilkadziesiąt razy na sekundę. Natomiast w przypadku matchMedia funkcja zostanie wywołana wyłącznie raz, gdy media query spełni warunek. A więc mówimy tu o optymalizacji nie dwukrotnej czy pięciokrotnej, ale nawet kilkudziesięciokrotnej.

      Najłatwiej sprawdzić to tworząc zmienną globalną var counter = 0, a następnie dodać do window zdarzenie resize, a w nim funkcję z console.log(++counter).

      Kiedy będziemy skalować okno przeglądarki, cała masa logów zostanie wyświetlona w konsoli. W przypadku matchMedia wyłącznie jeden.

      Pozdrawiam

  • Przydałaby się jakaś metoda na zupełne wyłączanie danych elementów w kodzie (tj. sprawić, aby dany kod nie był w ogóle generowany), a nie tylko ich ukrywanie za pomocą display:none; co jest nagminne. Użytkownikowi należy się mniej kodu i szybsze ładowanie strony na urządzeniach mobilnych, jakieś pomysły?:)

    • Po stronie klienta dowolny element możemy całkowicie usunąć z drzewa DOM, np. dzięki jQuery $("#elem").remove(), natomiast nie zmienia to faktu, że kod HTML z tym elementem został już przesłany do urządzenia użytkownika. Jedyny zatem sposób na usunięcie niechcianych elementów to zrobienie tego po stronie serwera, np. serwując inny szablon dla przeglądarek mobilnych, które można wykryć po ciągu userAgent.