Observer API

Resize observer

Obiekt typu Resize Observer pozwala nam reagować na zmiany rozmiaru obserwowanego elementu (lub elementów) na stronie. Dzięki temu nie musimy za każdym razem podłączać się pod zdarzenie resize dla window, co z pewnością wyjdzie na korzyść jeżeli chodzi o wydajność naszej strony.

Aby zacząć reagować musimy stworzyć nowy obiekt typu ResizeObserver, a następnie użyć go do obserwowania danych elementów:


//tworzymy observer
const observer = new ResizeObserver((elements, observer) => {
    //elements - tablica obserwowanych przez ten observer elementów
    //observer - dany observer

    for (let el of elements) {
        console.log(el);
        console.log(el.target); //element który właśnie się zmienił
    }
});

//zaczynamy obserwować jakieś elementy
const el = document.querySelector(".resize-element");
observer.observe(el);

Rozszerzając lewego diva zmień rozmiar prawego i sprawdź w konsoli:

Jestem tu tylko po to byś mnie rozszerzył :)
Lorem ipsum dolor sit amet consectetur adipisicing elit. Soluta doloribus autem, saepe consectetur hic minus quod laboriosam reprehenderit culpa illo.

Gdy spojrzysz na wynik, zobaczysz, że funkcja zwrotna daje nam dostęp do kilku przydatnych informacji:


const observer = new ResizeObserver((elements, observer) => {
    for (let el of elements) {
        entry.contentBoxSize //rozmiar treści elementu bez padding i border
        entry.borderBoxSize //rozmiar wraz z padding i borderem
        entry.contentRect //rozmiar treści
        entry.target //dany element który się zmienił
    }
});

Obiekt typu ResizeObserver daje nam dostęp do kilku funkcji:

observer.disconnect() Przestaje obserwować wszystkie obserwowane elementy
observer.observe(el) zaczyna obserwować dany element
observer.unobserve(el) przestaje obserwować dany element

Przez lata budowaliśmy (budujemy i będziemy budować) strony w oparciu o @media queries, dla których piszemy warunki w odniesieniu do całej szerokości strony. Od jakiegoś czasu możemy już eksperymentować z @container queries, które pozwalają nam pisać odpowiednie warunki w odniesieniu do rozmiaru elementu-rodzica.

Na pełne wsparcie tego zapisu musimy jeszcze chwilkę poczekać, natomiast nic nie stoi na przeszkodzie, by podobną rzecz zrobić za pomocą ResizeObserver:


.is-small {
    background: crimson;
}
.is-medium {
    background: dodgerblue;
}
.is-big {
    background: gold;
}
.is-large {
    background: var(--color-main);
}

const div = document.querySelector("div");

const breakpoints = {
    "small" : 0,
    "medium" : 200,
    "big" : 300,
    "large" : 500
}

const observer = new ResizeObserver(elements => {
    for (let el of elements) {
        //usuwam wszystkie klasy
        const classToDelete = Object.keys(breakpoints).map(key => `is-${key}`);
        el.target.classList.remove(...classToDelete);

        //dodaję odpowiednią klasę tylko jeżeli szerokość elementu jest większa niż danego breakpointa
        let className = "";
        for (let [key, val] of Object.entries(breakpoints)) {
            if (el.borderBoxSize[0].inlineSize > val) className = `is-${key}`;
        }
        if (className !== "") el.target.classList.add(className);
    }
});

btnAdd.addEventListener("click", e => {
    const newDiv = document.createElement("div")
    newDiv.textContent = ++count;
    div.append(newDiv);

    observer.observe(newDiv);
});

Oczywiście to tylko prosty przykład, który warto przeanalizować i w razie potrzeby zmodyfikować.

Mutation observer

Kolejny w kolejce to Mutation Observer, który służy do obserwowania zmian struktury html danych elementów.

Użycie tego obserwatora jest bardzo podobne do powyższego. Główną różnicą jest to, że tworząc nowy obiekt tego typu musimy poza elementem do obserwowania przekazać dodatkowe opcje, które wskażą co i jak obserwować.


let observer = new MutationObserver(mutations => {
    ...
});

const observerOptions = {
    childList: true, //czy obserwować dzieci danego elementu
    attributes: true, //czy obserwować atrybuty
    characterData: false, //czy obserwować zmiany tekstu
    subtree: false, //czy obserwować zmiany drzewa we wnętrzu obserwowanego elementu
    attributeFilter: ["title", "date-text"], //zmiany w jakich atrybutach mają być obserwowane
    attributeOldValue: false, //czy rejestrować wartości atrybutów przed zmianą
    characterDataOldValue: false, //czy rejestrować wartość tekstu sprzed zmiany
    subtree: true //false - jeżeli chcemy obserwować zmiany tylko danego elementu bez jego dzieci
}

const div = document.querySelector(".element");
observer.observe(div, observerOptions);

Podając opcje musimy ustawić przynajmniej jedną z pierwszej trójki na true. Cała reszta parametrów jest opcjonalna.

Jeżeli chodzi o metody to mamy dostęp do:

observer.observe(el) zaczyna obserwować dany element
observer.disconnect() wyłącza obserwowanie
observer.takeRecords() zwraca listę wszystkich zmian na DOM które zostały wykryte, ale jeszcze nie trafiły do funkcji zwrotnej, którą podłączyliśmy

Przykład użycia:


<div class="test-container">
    <div id="div" class="test-element">
        <div>Jakaś <strong>przykładowa treść</strong></div>
    </div>
    <button class="button" id="buttonChangeAttr">Zmień atrybut</button>
    <button class="button" id="buttonAdd">Dodaj element</button>
</div>

const div = document.querySelector("div");

const observer = new MutationObserver(mutationList => {
    for (let mutation of mutationList) {
        console.log(mutation);
    }
});

observer.observe(div, {
    childList: true,
    attributes: true,
    subtree: true
});

Sprawdź poniższy div w debugerze

Jakaś przykładowa treść

Dodatkowy artykuł na temat Mutation Observer znajdziesz pod adresem https://www.smashingmagazine.com/2019/04/mutationobserver-api-guide/

Intersection observer

Kolejnym obserwatorem jest Intersection observer, który służy do obserwowania czy dany element znajdzie się w widocznym obszarze ekranu lub przewijanego elementu.

Omawiane działanie możesz spotkać na wielu "fikuśnych" stronach, na których podczas przewijania nagle zaczynają się animować różne elementy.

Takie rozpoczęcie animacji możemy wykrywać na dwa sposoby. Stara metoda to podpięcie się pod zdarzenie scroll dla danego elementu (najczęściej window) i sprawdzanie w nim, czy dany element pojawił się na ekranie:


const el = document.querySelector(".element-to-check");

function checkAndToggle() {
    const pos = el.getBoundingClientRect(); //pobieram pozycję i rozmiar elementu
    const offset = pos.height / 2;
    const bottomEdge = pos.bottom - offset;
    const topEdge = post.bottom + offset;
    if (bottomEdge > window.scrollY || topEdge < window.scrollY + window.innerHeight) {
        //jest
    } else {
        //nie ma
    }
}

window.addEventListener("scroll", checkAndToggle);

Nie jest to jednak idealne rozwiązanie, ponieważ działanie bezpośrednio na zdarzeniu scroll jest mocno obciążające dla przeglądarki (szczególnie gdy w powyższy sposób obsługujemy wiele elementów na stronie). Zamiast tego do takich testów o wiele lepiej skorzystać z omawianego obserwatora.

Jego użycie jest bliźniacze do powyższych, różnica jest tak naprawdę w przekazanych opcjach i ewentualnie danych, do których mamy dostęp w funkcji zwrotnej.


let observerOptions = {
    root: document.querySelector('#scrollArea'),
    rootMargin: '0px',
    threshold: 0.5
}

let observer = new IntersectionObserver(elements => {
    for (let el of elements) {
        if (el.isIntersecting >= 1) {
            el.target.style.background = "dodgerblue";
        } else {
            el.target.style.background = "";
        }
    }
}, options);

const div = document.querySelector("div");
observer.observe(div);

Tworząc nowy obiekt tego typu powinniśmy przekazać obiekt z dodatkowymi opcjami:

root Element który staje się obszarem widoczności. Możemy podać null - wtedy obszarem staje się viewport
rootMargin Margines na około roota, który pozwala zmniejszyć lub powiększyć jego obszar na którym będzie testowany element. Wartość podobna do marginesu z css to znaczy "10px 20px 30px 40px". Możemy ją podać w dowolnej jednostce.
threshold Określa moment w którym ma być reakcja na pojawienie się elementu w obszarze widoczności. Wartość 0 określa że reakcja będzie jak tylko element zacznie się wyłaniać spoza ekranu. Wartość 0.5 oznacza, że reakcja będzie gdy element pojawi się w połowie, 1 oznacza, że reakcja będzie gdy cały pokaże się w widocznym obszarze. Możemy też podać tablicę wartości które będą oznaczać kolejne poziomy widoczności np. [0, 0.25, 0.5]

I tak samo jak w poprzednich obserwatorach mamy dostęp do dodatkowych metody:

observer.observe(el) zaczyna obserwować dany element
observer.disconnect() wyłącza obserwowanie
observer.unobserve(el) wyłącza obserwowanie danego elementu
observer.takeRecords() zwraca listę wszystkich zmian na DOM które zostały wykryte, ale jeszcze nie trafiły do funkcji zwrotnej, którą podłączyliśmy

Przykład użycia:

Lorem ipsum dolor, sit amet consectetur adipisicing elit. Placeat odio minima officiis corporis cupiditate labore molestiae excepturi deserunt officia quasi velit doloribus commodi tempora unde ad iusto vitae laudantium, animi esse optio repellat autem distinctio perspiciatis quidem? Deleniti quis quibusdam minus recusandae fugiat ratione, neque consectetur expedita. Sapiente doloremque, nisi facilis nam, sit sed voluptatum nostrum omnis, quae dignissimos eligendi minus. Laborum adipisci eum repellat eaque sed recusandae itaque quas quisquam amet. Aspernatur dignissimos, excepturi quam, facere doloremque, itaque quis officiis nam inventore vel amet temporibus unde perferendis harum culpa soluta aliquid totam? Magni, unde amet dolorem quas incidunt rem quia obcaecati id earum sed, dicta labore veniam voluptatem ullam modi atque velit facilis? Rerum obcaecati nesciunt error voluptatem eveniet odio perferendis libero quis ab eos alias, quasi incidunt accusamus ullam necessitatibus sint fugit impedit atque unde. Odio repellat maxime sequi vero, quae provident neque, ducimus unde rerum quas nesciunt veniam, aliquam eius illo possimus cum in magnam? Quae, veniam inventore nesciunt iusto, sed suscipit porro omnis magni possimus magnam labore. Vel repellendus animi nihil quo, labore veritatis cumque minima itaque eius odit, ducimus molestias nulla harum architecto earum iure quos. Voluptas earum inventore quas consequuntur suscipit nam culpa laborum!
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Blanditiis eveniet facilis illo, sunt voluptates libero placeat aliquid accusantium accusamus. Praesentium eum nisi similique asperiores, quod possimus a corrupti, repellendus sed tempora dignissimos maxime error natus reiciendis consequuntur animi esse nobis, sapiente eius? Officiis minus quas fugit quae, laboriosam provident corrupti sit perferendis porro sint fuga numquam, facilis consectetur soluta debitis atque nam, error aperiam vel consequatur dolores nisi dolorem. Numquam sequi corrupti fuga temporibus possimus voluptatum ab fugit error architecto distinctio eaque officia repudiandae perferendis sit, provident praesentium dolorem est aliquid unde minus rerum voluptas cumque accusantium quia? Fugiat dolorum dolores atque incidunt praesentium assumenda eum dignissimos aliquam possimus. Praesentium aliquam quae nostrum modi veritatis repellat repudiandae ducimus eligendi unde inventore aliquid a blanditiis facilis dicta temporibus officia commodi deserunt veniam voluptatibus excepturi natus, voluptatem, ex dolorem eum. Sit, cumque. Reiciendis ex quidem quia est, esse, minima fugiat ea iure voluptate optio adipisci id enim ullam molestias tempore porro quisquam, harum quo illo assumenda incidunt architecto commodi dolorum cumque? Dolorem quam exercitationem similique impedit possimus harum esse adipisci iste inventore culpa libero laborum deserunt numquam dolores molestiae accusamus eius, porro earum commodi error! Expedita ipsum corporis labore dolorem eum! Voluptatum.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.