MutationObserver est un objet intĂ©grĂ© qui observe un Ă©lĂ©ment DOM et dĂ©clenche une callback (fonction de rappel) lorsquâil dĂ©tecte un changement.
Nous examinerons dâabord la syntaxe, puis nous Ă©tudierons un cas dâutilisation rĂ©el, pour voir oĂč ce genre de chose peut ĂȘtre utile.
Syntaxe
MutationObserver est facile Ă utiliser.
Tout dâabord, nous crĂ©ons un observateur avec un callback:
let observer = new MutationObserver(callback);
Et ensuite on lâattache Ă un nĆud DOM:
observer.observe(node, config);
config est un objet avec des options boolĂ©ennes âsur quel type de changements rĂ©agirâ:
childListâ les changements dans les enfants directs denode,subtreeâ dans tous les descendants denode,attributesâ dans les attributs denode,attributeFilterâ dans un tableau de noms dâattributs, pour nâobserver que ceux qui sont sĂ©lectionnĂ©s,characterDataâ sâil faut observernode.data(contenu du texte),
Quelques autres options:
attributeOldValueâ sitrue, passer lâancienne et la nouvelle valeur de lâattribut au callback (voir ci-dessous), sinon, seule la nouvelle valeur (a besoin de lâoptionattributes).characterDataOldValueâ sitrue, passer lâancienne et la nouvelle valeur denode.dataau callback (voir ci-dessous), sinon, seule la nouvelle valeur (a besoin de lâoptioncharacterData)
Ensuite, aprĂšs tout changement, le callback est exĂ©cutĂ© : les changements sont passĂ©s dans le premier argument comme une liste dâobjets MutationRecord, et lâobserver lui-mĂȘme comme deuxiĂšme argument.
Les objects MutationRecord ont les propriétés suivantes:
typeâ type de mutation, valeurs possibles:"attributes": attribut modifiĂ©,"characterData": donnĂ©es modifiĂ©es, utilisĂ©es pour les nĆuds de texte,"childList": Ă©lĂ©ments enfants ajoutĂ©s/supprimĂ©s,
targetâ oĂč le changement a eu lieu: un Ă©lĂ©ment pour lesattributes, ou un nĆud de texte pour lescharacterData, ou un Ă©lĂ©ment pour une mutationchildList,addedNodes/removedNodesâ les nĆuds qui ont Ă©tĂ© ajoutĂ©s/supprimĂ©s,previousSibling/nextSiblingâ le frĂšre ou la sĆur prĂ©cĂ©dent(e) et suivant(e) aux nĆuds ajoutĂ©s/supprimĂ©s,attributeName/attributeNamespaceâ le nom/espace de nommage (pour XML) de lâattribut modifiĂ©,oldValueâ la valeur prĂ©cĂ©dente, uniquement pour les modifications dâattributs ou de texte, si lâoption correspondante est dĂ©finieattributeOldValue/characterDataOldValue.
Par exemple, voici un <div> avec un attribut contentEditable. Cet attribut nous permet de âfocusâ contenu et de lâĂ©diter.
<div contentEditable id="elem">Click and <b>edit</b>, please</div>
<script>
let observer = new MutationObserver(mutationRecords => {
console.log(mutationRecords); // console.log(les changements)
});
// observer tout sauf les attributs
observer.observe(elem, {
childList: true, // observer les enfants directs
subtree: true, // et les descendants aussi
characterDataOldValue: true // transmettre les anciennes données au callback
});
</script>
Si nous exĂ©cutons ce code dans le navigateur, puis quâon focus la <div> donnĂ© et changeons le texte Ă lâintĂ©rieur de <b>edit</b>, console.log affichera une mutation:
mutationRecords = [{
type: "characterData",
oldValue: "edit",
target: <text node>,
// autres propriétés vides
}];
Si nous effectuons des opĂ©rations dâĂ©dition plus complexes, par exemple en supprimant le <b>edit</b>, lâĂ©vĂ©nement de mutation peut contenir plusieurs enregistrements de mutation:
mutationRecords = [{
type: "childList",
target: <div#elem>,
removedNodes: [<b>],
nextSibling: <text node>,
previousSibling: <text node>
// autres propriétés vides
}, {
type: "characterData"
target: <text node>
// ...les détails de la mutation dépendent de la façon dont le navigateur gÚre cette suppression
// il peut regrouper deux nĆuds de texte adjacents "edit" et ", please" en un seul nĆud
// ou il peut leur laisser des nĆuds de texte sĂ©parĂ©s
}];
MutationObserver permet donc de réagir à tout changement dans le sous-arbre DOM
Utilisation pour lâintĂ©gration
Quand une telle chose peut-elle ĂȘtre utile ?
Imaginez la situation oĂč vous devez ajouter un script tiers qui contient des fonctionnalitĂ©s utiles, mais qui fait aussi quelque chose dâindĂ©sirable, par exemple afficher des annonces <div class="ads">Unwanted ads</div>.
Naturellement, le script tiers ne prévoit aucun mécanisme permettant de le supprimer.
GrĂące Ă MutationObserver, nous pouvons dĂ©tecter quand lâĂ©lĂ©ment indĂ©sirable apparaĂźt dans notre DOM et le supprimer.
Il y a dâautres situations oĂč un script tiers ajoute quelque chose dans notre document, et nous aimerions dĂ©tecter, quand cela se produit, dâadapter notre page, de redimensionner dynamiquement quelque chose, etc.
MutationObserver permet de faire tout ça.
Utilisation pour lâarchitecture
Il y a aussi des situations oĂč MutationObserver est bon du point de vue architectural.
Disons que nous faisons un site web sur la programmation. Naturellement, les articles et autres matériels peuvent contenir des extraits de code source.
Voici Ă quoi ressemble un tel extrait dans un balisage HTML:
...
<pre class="language-javascript"><code>
// voici le code
let hello = "world";
</code></pre>
...
Pour une meilleure lisibilitĂ© et en mĂȘme temps, pour lâembellir, nous utiliserons une bibliothĂšque de coloration syntaxique JavaScript sur notre site, comme Prism.js. Pour obtenir la coloration syntaxique de lâextrait de code ci-dessus dans Prism, Prism.highlightElem(pre) est appelĂ©, qui examine le contenu de ces Ă©lĂ©ments pre et ajoute des balises et des styles spĂ©ciaux pour la coloration syntaxique colorĂ©e dans ces Ă©lĂ©ments, similaire Ă ce que vous voyez en exemples ici, sur cette page.
Quand exactement faut-il appliquer cette mĂ©thode de mise en Ă©vidence ? Nous pouvons le faire sur lâĂ©vĂ©nement DOMContentLoaded, ou en bas de page. Ă ce moment, nous avons notre DOM prĂȘt, nous pouvons rechercher des Ă©lĂ©ments pre[class*="language"] et appeler Prism.highlightElem dessus :
// mettre en évidence tous les extraits de code sur la page
document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem);
Tout est simple jusquâĂ prĂ©sent, nâest-ce pas ? Nous trouvons des extraits de code en HTML et les mettons en Ă©vidence.
Maintenant, continuons. Disons que nous allons chercher dynamiquement des Ă©lĂ©ments sur un serveur. Nous Ă©tudierons les mĂ©thodes pour cela plus tard dans le tutoriel. Pour lâinstant, il suffit dâaller chercher un article HTML sur un serveur web et de lâafficher Ă la demande :
let article = /* récupérer du nouveau contenu sur le serveur */
articleElem.innerHTML = article;
Le nouvel article HTML peut contenir des extraits de code. Nous devons appeler Prism.highlightElem sur eux, sinon ils ne seront pas mis en évidence.
OĂč et quand appeler Prism.highlightElem pour un article chargĂ© dynamiquement ?
Nous pourrions ajouter cet appel au code qui charge un article, comme ceci:
let article = /* récupérer du nouveau contenu sur le serveur */
articleElem.innerHTML = article;
let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
snippets.forEach(Prism.highlightElem);
⊠Mais imaginez si nous avons de nombreux endroits dans le code oĂč nous chargeons notre contenu â articles, quiz, messages de forum, etc. Devons-nous mettre lâappel de mise en Ă©vidence partout, pour mettre en Ă©vidence le code dans le contenu aprĂšs le chargement? Ce nâest pas trĂšs pratique.
Et si le contenu est chargĂ© par un module tiers ? Par exemple, nous avons un forum Ă©crit par quelquâun dâautre, qui charge le contenu dynamiquement, et nous aimerions y ajouter une mise en Ă©vidence syntaxique. Personne nâaime patcher des scripts tiers.
Heureusement, il y a une autre option.
Nous pouvons utiliser MutationObserver pour détecter automatiquement quand des extraits de code sont insérés dans la page et les mettre en évidence.
Nous allons donc gérer la fonctionnalité de mise en évidence en un seul endroit.
Démonstration dynamique de mise en évidence
Si vous exĂ©cutez ce code, il commence Ă observer lâĂ©lĂ©ment ci-dessous et Ă mettre en Ă©vidence tout extrait de code qui y apparaĂźt:
let observer = new MutationObserver(mutations => {
for(let mutation of mutations) {
// examiner les nouveaux nĆuds, y a-t-il quelque chose Ă mettre en Ă©vidence ?
for(let node of mutation.addedNodes) {
// nous ne suivons que les Ă©lĂ©ments, nous sautons les autres nĆuds (par exemple les nĆuds de texte)
if (!(node instanceof HTMLElement)) continue;
// vérifier que l'élément inséré est un extrait de code
if (node.matches('pre[class*="language-"]')) {
Prism.highlightElement(node);
}
// ou peut-ĂȘtre qu'il y a un extrait de code quelque part dans son sous-arbre ?
for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
Prism.highlightElement(elem);
}
}
}
});
let demoElem = document.getElementById('highlight-demo');
observer.observe(demoElem, {childList: true, subtree: true});
Ci-dessous, il y a un élément HTML et JavaScript qui le remplit dynamiquement en utilisant innerHTML.
Veuillez exĂ©cuter le code prĂ©cĂ©dent (ci-dessus, qui observe cet Ă©lĂ©ment), puis le code ci-dessous. Vous verrez comment MutationObserver dĂ©tecte et met en Ă©vidence lâextrait.
Voici un élément de démonstration avec id="highlight-demo", exécutez le code ci-dessus pour l'observer.
Le code suivant remplit son innerHTML, qui fait réagir le MutationObserver et met en évidence son contenu:
let demoElem = document.getElementById('highlight-demo');
// insérer dynamiquement du contenu avec des extraits de code
demoElem.innerHTML = `Vous trouverez ci-dessous un extrait de code:
<pre class="language-javascript"><code> let hello = "world!"; </code></pre>
<div>Un autre:</div>
<div>
<pre class="language-css"><code>.class { margin: 5px; } </code></pre>
</div>
`;
Nous avons maintenant MutationObserver qui peut suivre tous les surlignages dans les éléments observés ou dans le document entier. Nous pouvons ajouter/supprimer des bribes de code en HTML sans y penser.
Méthodes supplémentaires
Il y a une mĂ©thode pour arrĂȘter dâobserver le nĆud:
observer.disconnect()â arrĂȘte lâobservation.
Lorsque nous arrĂȘtons lâobservation, il est possible que certaines modifications nâaient pas encore Ă©tĂ© traitĂ©es par lâobservateur.
observer.takeRecords()â obtient une liste des dossiers de mutation non traitĂ©s, ceux qui se sont produits, mais le rappel nâa pas permis de les traiter.
Ces mĂ©thodes peuvent ĂȘtre utilisĂ©es ensemble, comme ceci:
// obtenir une liste des mutations non traitées
// doit ĂȘtre appelĂ© avant de se dĂ©connecter,
// si vous vous souciez de mutations récentes éventuellement non gérées
let mutationRecords = observer.takeRecords();
// stop tracking changes
observer.disconnect();
...
Le rappel ne sera pas appelé pour les enregistrements, renvoyé par observer.takeRecords().
Les observateurs utilisent des rĂ©fĂ©rences faibles aux nĆuds en interne. Autrement dit, si un nĆud est retirĂ© du DOM et devient inaccessible, il devient alors un dĂ©chet collectĂ©.
Le simple fait quâun nĆud DOM soit observĂ© nâempĂȘche pas le ramassage des ordures.
Résumé
MutationObserver peut rĂ©agir aux changements dans le DOM â attributs, contenu de texte et ajout / suppression dâĂ©lĂ©ments.
Nous pouvons lâutiliser pour suivre les changements introduits par dâautres parties de notre code, ainsi que pour intĂ©grer des scripts tiers.
MutationObserver peut suivre tout changement. Les options de configuration âce quâil faut observerâ sont utilisĂ©es pour des optimisations, afin de ne pas dĂ©penser des ressources pour des callback inutiles.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâŠ)