Praca z RWD

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 desktopowa");
    } 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 w CSS za pomocą mediaQuery 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ą JavaScript 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 na desktopy");
    } 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 na desktopy');
} 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 sprawdzanie 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 na desktopy');
} else {
    console.log('Wersja mobilna');
}

media.addListener(function(media) {
    if (media.matches) {
        console.log('Wersja na desktopy');
    } 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);

Trening czyni mistrza

Poniżej zamieszczam kilka zadań, które w ramach ćwiczenia możesz wykonać:

  1. Dodaj na stronę diva:
    
                    div {
                        width: 200px;
                        height: 200px;
                        background: red;
                    }
                

    Załóżmy (złe to założenie, ale na potrzeby ćwiczenia niech zostanie), że Po kliknięciu na ten div wypisz w konsoli tekst "kliknięto na div".
    Event powinien działać tylko na urządzeniach o szerokości obszaru wyświetlanego od 500px w górę.
    Dodatkowo na urządzeniach powyżej 500px ustaw tło diva na czerwony, a na mniejszych na niebieski.

    function clickOnDiv() { console.log("Kliknięto na div"); } const div = document.querySelector("div"); const bp = "(min-width: 500px)"; const media = matchMedia(bp); if (media.matches) { div.addEventListener("click", clickOnDiv); div.style.background = "red"; } else { div.removeEventListener("click", clickOnDiv); //nawet jak nie ma eventu nic się nie stanie div.style.background = "blue"; } media.addListener(function(media) { if (media.matches) { div.addEventListener("click", clickOnDiv); div.style.background = "red"; } else { div.removeEventListener("click", clickOnDiv); div.style.background = "blue"; } });
  2. Ściągnij sobie stronę z przykładowym zadaniem.
    Jest tam nagłówek .header.

    Twoim zadaniem jest napisać kod, który podczas przewijania okna sprawdzi pozycję paska przewijania (scrollY). Jeżeli będzie ona większa niż 100px, niech skrypt doda do nagłówka klasę .sticky. Jeżeli pozycja będzie mniejsza niech tą klasę usunie.

    Nagłówek ten ma pozycjonowanie fixed, więc jest przypięty do ekranu. Dla małych ekranów (max 600px szerokości) nagłówek zmienia pozycjonowanie na absolutne, więc powyższy kod nie będzie miał sensu. Dopisz dodatkowy kod, który powyższe dodawanie klasy będzie wykonywał tylko dla ekranów >= 600px.

    
                document.addEventListener("DOMContentLoaded", function() {
                    const header = document.querySelector('.header');
                    const media = matchMedia("(min-width: 600px)");
    
                    function addSticky() {
                        if (window.scrollY > 100) {
                            header.classList.add('sticky');
                            console.log('s')
                        } else {
                            header.classList.remove('sticky');
                        }
                    }
    
                    if (media.matches) {
                        window.addEventListener('scroll', addSticky);
                    } else {
                        window.removeEventListener('scroll', addSticky);
                    }
    
                    media.addListener(function(media) {
                        if (media.matches) {
                            window.addEventListener('scroll', addSticky);
                        } else {
                            window.removeEventListener('scroll', addSticky);
                        }
                    });
                });