MutationObserver
Obserwuje zmiany w drzewie DOM: dodane i usunięte elementy, zmiany atrybutów i tekstu, i zgłasza je w jednej funkcji.
- Zwraca
- Nowy obiekt
MutationObserver.
Przykład
#const chat = document.querySelector('#chat')
const unread = document.querySelector('#unread')
const observer = new MutationObserver((mutations) => {
console.log(`Zmian w paczce: ${mutations.length}`)
for (const mutation of mutations) {
mutation.addedNodes.forEach((node) => console.log('Nowa wiadomość:', node.textContent))
}
unread.textContent = chat.children.length - 1
})
observer.observe(chat, { childList: true })
function addMessage(text) {
const item = document.createElement('li')
item.textContent = text
chat.append(item)
}
addMessage('Kuba: Hej!')
addMessage('Ola: Kto robi quiz?')
console.log('Kod synchroniczny zakończony')Obie wiadomości dodano jedna po drugiej, więc obserwator dostał je w jednym wywołaniu, dopiero po zakończeniu kodu synchronicznego.
Definicja i zastosowanie
#MutationObserver pozwala zareagować na zmiany w strukturze strony, niezależnie od tego, który fragment kodu je wprowadził. Przydaje się, gdy treść zmienia inny skrypt, biblioteka albo wtyczka i nie masz dostępu do miejsca, w którym to się dzieje, np. przy liczeniu nowych wiadomości w czacie albo pilnowaniu atrybutów widżetu.
Metoda observe(element, opcje) wymaga wskazania, co obserwować: childList (dodawanie i usuwanie dzieci), attributes (zmiany atrybutów) albo characterData (zmiany tekstu w węzłach tekstowych). Opcja subtree rozszerza obserwację na wszystkich potomków, attributeFilter zawęża ją do wybranych atrybutów, a attributeOldValue zapamiętuje ich poprzednie wartości.
Funkcja zwrotna dostaje tablicę obiektów MutationRecord z polami takimi jak type, target, addedNodes, removedNodes i attributeName. Zmiany są zbierane i przekazywane razem jako mikrozadanie, czyli po zakończeniu bieżącego kodu, ale przed timerami. Kilka zmian wykonanych jedna po drugiej trafi więc do jednego wywołania.
Metoda disconnect() kończy obserwację, a takeRecords() zwraca zebrane, jeszcze nieprzekazane zmiany. Unikaj obserwowania całego dokumentu z subtree: true, jeśli nie jest to konieczne, bo przy dużych zmianach funkcja zwrotna będzie wywoływana bardzo często.
Składnia
#new MutationObserver(callback)
observer.observe(target, options)
observer.disconnect()
observer.takeRecords()Parametry
#callback
function (mutations, observer)
Funkcja wywoływana po zmianach. Dostaje tablicę obiektówMutationRecord.target
węzeł
Element (lub inny węzeł), od którego zaczyna się obserwacja.options
obiekt
Co najmniej jedna z opcjichildList,attributeslubcharacterDataustawiona natrue, a do tego opcjonalniesubtree,attributeFilter,attributeOldValueicharacterDataOldValue.
Więcej przykładów
#const lesson = document.querySelector('#lesson')
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
console.log(`${mutation.attributeName}, poprzednia wartość: ${mutation.oldValue}`)
}
console.log('Aktualny status:', lesson.dataset.status)
})
observer.observe(lesson, { attributeFilter: ['data-status'], attributeOldValue: true })
lesson.dataset.status = 'in-progress'
lesson.dataset.status = 'done'
lesson.title = 'Ukończona'Zmiana title nie trafia do obserwatora, bo attributeFilter ogranicza obserwację do data-status. Każda zmiana ma osobny wpis z wartością sprzed zmiany.
const editor = document.querySelector('#editor')
const note = document.querySelector('#note')
const observer = new MutationObserver((mutations) => {
console.log(mutations.map((mutation) => mutation.type))
})
observer.observe(editor, { childList: true, characterData: true, subtree: true })
note.firstChild.data = 'Notatka o pętlach'
editor.append(document.createElement('hr'))
setTimeout(() => {
observer.disconnect()
note.firstChild.data = 'Tej zmiany nikt nie zobaczy'
console.log('Obserwacja zakończona')
}, 100)Zmiana tekstu w węźle tekstowym to characterData, a dodanie <hr> to childList. Tekst leży głębiej niż #editor, dlatego potrzebna jest opcja subtree. Po disconnect() kolejne zmiany nie są już zgłaszane.
Obsługa przeglądarek
#Szeroko dostępne
Działa we wszystkich nowoczesnych przeglądarkach, także na telefonach. Możesz używać bez obaw.
- Chrome
- Edge
- Firefox
- Safari
Dobre praktyki
#- Zawsze podawaj w opcjach, co obserwować. Samo
observe(element)bezchildList,attributesanicharacterDatarzuci błądTypeError. - Nie zmieniaj obserwowanego fragmentu w funkcji zwrotnej bez warunku. Każda taka zmiana wywoła obserwatora ponownie i łatwo o nieskończoną pętlę.
- Jeśli sam zmieniasz DOM w swoim kodzie, prościej wywołać odpowiednią funkcję zaraz po zmianie. Obserwator przydaje się, gdy zmiany wprowadza kod, na który nie masz wpływu.
Powiązane hasła
#- IntersectionObserverObserwuje, czy element jest widoczny w oknie albo w przewijanym kontenerze, i wywołuje funkcję, gdy to się zmienia.
- ResizeObserverWywołuje funkcję za każdym razem, gdy zmieni się rozmiar obserwowanego elementu, bez względu na przyczynę zmiany.
- Element.append()Dodaje elementy lub tekst na końcu zawartości elementu.
- Element.setAttribute()Ustawia wartość atrybutu HTML elementu albo dodaje nowy atrybut.
- HTMLElement.datasetObiekt z wartościami atrybutów data-* elementu, do odczytu i zapisu własnych danych.
Widzisz błąd albo brakuje przykładu? Napisz do nas.