Dans les applications Web complexes, les modifications DOM peuvent être fréquentes. Par conséquent, il peut arriver que votre application doive réagir à une modification spécifique du DOM.
Pendant un certain temps, le moyen le plus courant de rechercher des modifications dans le DOM consistait à utiliser une fonctionnalité appelée Mutation. Events qui est maintenant obsolète . Le remplacement approuvé par le W3C pour Mutation Events est l'API MutationObserver ce dont je vais parler en détail dans cet article.
Un certain nombre d'articles plus anciens et de références expliquent pourquoi l'ancienne fonctionnalité a été remplacée. donc je n’entrerai pas dans les détails ici (à part le fait que je ne serais pas capable de le faire justice). L'API MutationObserver
prend en charge de manière presque complète dans le navigateur, de sorte que nous pouvons l'utiliser en toute sécurité dans la plupart des projets, voire tous, si le besoin s'en faisait sentir.
Syntaxe de base pour un serveur MutationObserver
19659006] Un MutationObserver
peut être utilisé de différentes manières, que je traiterai en détail dans la suite de cet article, mais la syntaxe de base pour un MutationObserver
ressemble à ceci : let observ = nouveau MutationObserver (rappel);
fonction callback (mutations) {
// fais quelque chose ici
}
observer.observer (targetNode, observerOptions);
La première ligne crée un nouveau MutationObserver
à l'aide du constructeur MutationObserver ()
. L'argument transmis au constructeur est une fonction de rappel qui sera appelée à chaque modification DOM qualifiée.
La manière de déterminer ce qui est qualifié pour un observateur particulier consiste à utiliser la dernière ligne du code ci-dessus. Sur cette ligne, j’utilise la méthode observe ()
de la MutationObserver
pour commencer à observer. Vous pouvez comparer cela à quelque chose comme addEventListener ()
. Dès que vous attachez un écouteur, la page "écoute" l’événement spécifié. De même, lorsque vous commencez à observer, la page commence à «observer» pour la MutationObserver
spécifiée.
La méthode observe ()
prend deux arguments: La cible ]qui devrait être le nœud ou l’arborescence de nœuds sur lequel observer les modifications; et un objet options qui est un objet MutationObserverInit
qui vous permet de définir la configuration pour l'observateur.
La dernière fonctionnalité de base essentielle d'un MutationObserver
. est la méthode disconnect ()
. Cela vous permet d’arrêter d’observer les modifications spécifiées et vous donne l’image suivante:
observer.disconnect ();
Options pour configurer un MutationObserver
Comme indiqué, la méthode observe ()
d'un MutationObserver
requiert un deuxième argument spécifiant les options permettant de décrire le MutationObserver .
. Voici à quoi ressemblerait l’objet options avec toutes les paires propriété / valeur possibles incluses:
let options = {
childList: true,
attributs: vrai,
characterData: false,
sous-arbre: faux,
attributFilter: ['one', 'two'],
attributOldValue: false,
characterDataOldValue: false
};
Lors de la configuration des options MutationObserver
il n’est pas nécessaire d’inclure toutes ces lignes. Je les inclut simplement à des fins de référence afin que vous puissiez voir quelles options sont disponibles et quels types de valeurs elles peuvent prendre. Comme vous pouvez le constater, tous sauf un sont booléens.
Pour qu'un MutationObserver
fonctionne, au moins un des childList
attributs
ou characterData
doit être défini sur true
sinon une erreur sera générée. Les quatre autres propriétés fonctionnent conjointement avec l’une de ces trois propriétés (voir plus loin).
Jusqu’à présent, j’ai simplement passé en revue la syntaxe pour vous donner un aperçu. Le meilleur moyen d’examiner le fonctionnement de chacune de ces fonctionnalités consiste à fournir des exemples de code et des démonstrations en direct intégrant les différentes options. C’est ce que je ferai pour le reste de cet article.
Observer les modifications apportées aux éléments enfants à l’aide de childList
La première et la plus simple MutationObserver
que vous pouvez initier est celle qui recherche les nœuds enfants d’un noeud spécifié (généralement un élément) à ajouter ou à supprimer. Pour mon exemple, je vais créer une liste non ordonnée dans mon code HTML et je veux savoir à chaque fois qu'un nœud enfant est ajouté ou supprimé de cet élément de la liste.
Le code HTML de la liste ressemble à ceci:
- Pommes
- Oranges
- Bananes
- Pêches
Le code JavaScript de mon MutationObserver
comprend les éléments suivants:
let mList = document.getElementById ('myList'),
options = {
childList: true
},
observateur = new MutationObserver (mCallback);
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'childList') {
console.log ('Mutation détectée: un noeud enfant a été ajouté ou supprimé.');
}
}
}
observateur.observer (liste, options);
Ceci n'est qu'une partie du code. Par souci de brièveté, je montre les sections les plus importantes qui traitent de l’API MutationObserver
elle-même.
Remarquez comment je passe en revue l’argument des mutations
qui est un . ] MutationRecord
objet qui possède plusieurs propriétés différentes. Dans ce cas, je lis la propriété de type
et enregistre un message indiquant que le navigateur a détecté une mutation qualifiée. Vous remarquerez également que je passe l'élément mList
(une référence à ma liste HTML) en tant qu'élément ciblé (c'est-à-dire l'élément sur lequel je souhaite observer des modifications).
Utilisez les boutons pour démarrez et arrêtez le MutationObserver
. Les messages du journal aident à clarifier ce qui se passe. Les commentaires dans le code fournissent également des explications.
Notez quelques points importants ici:
- La fonction de rappel (que j'ai nommée
mCallback
pour illustrer le fait que vous pouvez l'appeler comme vous voulez). se déclenche à chaque fois qu'une mutation réussie est détectée et après l'exécution de la méthode observe ()
- Dans mon exemple, le seul "type" de mutation qui se qualifie est
childList
. il est donc logique de chercher celui-ci lorsque vous parcourez MutationRecord. La recherche de tout autre type dans cet exemple ne ferait rien (les autres types seront utilisés dans les démos suivants).
- À l'aide de
childList
je peux ajouter ou supprimer un nœud de texte de l'élément ciblé, et cela également. serait admissible. Il n’est donc pas nécessaire que ce soit un élément ajouté ou supprimé.
- Dans cet exemple, seuls les noeuds enfant immédiats seront qualifiés. Nous verrons plus loin dans cet article comment cela peut s'appliquer à tous les nœuds enfants, petits-enfants, etc.
Observer les modifications apportées aux attributs d'un élément
Un autre type de mutation commun que vous pouvez suivre est le suivant: lorsqu'un attribut d'un élément spécifié change. Dans la prochaine démonstration interactive, je vais observer les modifications apportées aux attributs d’un élément de paragraphe.
laissez mPar = document.getElementById ('myParagraph'),
options = {
attributs: vrai
},
observateur = new MutationObserver (mCallback);
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'attributs') {
// Faites quelque chose ici ...
}
}
}
observateur.observer (mPar, options);
Encore une fois, j'ai abrégé le code pour plus de clarté, mais les parties importantes sont les suivantes:
- L'objet
options
utilise la propriété d'attributs
définie sur .
pour indiquer à MutationObserver
que je souhaite rechercher des modifications dans les attributs de l'élément ciblé.
- Le type de mutation que je teste dans ma boucle est
attributs
le
- J'utilise également la propriété
attributeName
de l'objet mutation
ce qui me permet de déterminer quel attribut a été modifié.
- Lorsque je déclenche l'observateur, je passe l'élément de paragraphe par référence, ainsi que les options.
Dans cet exemple, un bouton permet de basculer le nom d'une classe sur l'élément HTML ciblé. La fonction de rappel dans l'observateur de mutation est déclenchée chaque fois que la classe est ajoutée ou supprimée.
Observer les modifications apportées aux données de personnage
Vous pouvez également rechercher d'autres modifications dans votre application, à savoir les mutations des données de personnage; c'est-à-dire les modifications apportées à un nœud de texte spécifique. Pour ce faire, définissez la propriété characterData
sur true
dans l'objet options
. Voici le code:
let options = {
characterData: true
},
observateur = new MutationObserver (mCallback);
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'characterData') {
// Faites quelque chose ici ...
}
}
}
Remarquez à nouveau que le type
recherché dans la fonction de rappel est characterData
.
Dans cet exemple, je cherche à modifier un nœud de texte spécifique, que je target via element.childNodes [0]
. C'est un peu hacky mais cela conviendra pour cet exemple. Le texte peut être modifié par l'utilisateur via l'attribut contenteditable
d'un élément de paragraphe.
Défis lors de l'observation des modifications de données de caractères
Si vous avez manipulé de manière à ce qu'il soit modifiable
alors vous savez peut-être qu'il existe des raccourcis clavier permettant l'édition de texte enrichi. Par exemple, CTRL-B met le texte en gras, CTRL-I rend le texte en italique, etc. Cela divisera le nœud de texte en plusieurs nœuds de texte, vous remarquerez donc que le MutationObserver
cessera de répondre à moins que vous ne modifiiez le texte qui est toujours considéré comme faisant partie du nœud d'origine.
Je devrais également signaler. que si vous supprimez tout le texte, le MutationObserver
ne déclenchera plus le rappel. Je suppose que cela se produit car une fois que le nœud de texte a disparu, l’élément cible n’existe plus. Pour lutter contre cela, ma démo cesse d’observer lorsque le texte est supprimé, même si les raccourcis en texte enrichi deviennent un peu collants.
Mais ne vous inquiétez pas, dans la suite de cet article, je discuterai d’un meilleur moyen de utilisez l'option characterData
sans avoir à gérer autant de problèmes de ce genre.
Observer les modifications apportées aux attributs spécifiés
Je vous ai déjà montré comment observer les modifications apportées aux attributs d'un élément spécifié. Dans ce cas, bien que la démonstration déclenche un changement de nom de classe, j'aurais pu changer n'importe quel attribut de l'élément spécifié. Mais que faire si je veux observer des modifications dans un ou plusieurs attributs spécifiques tout en ignorant les autres?
Je peux le faire en utilisant la propriété facultative attributeFilter
dans l'objet option
. Voici un exemple:
let options = {
attributs: vrai,
attributFilter: ['hidden', 'contenteditable', 'data-par']
},
observateur = new MutationObserver (mCallback);
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'attributs') {
// Faites quelque chose ici ...
}
}
}
Comme indiqué ci-dessus, la propriété attributeFilter
accepte un tableau d'attributs spécifiques que je souhaite surveiller. Dans cet exemple, le MutationObserver
déclenchera le rappel à chaque fois qu'un ou plusieurs des attributs cachés
contenteditable
ou data-par
est modifié.
Encore une fois, je vise un élément de paragraphe spécifique. Notez le menu déroulant de sélection qui choisit l’attribut à modifier. L'attribut draggable
est le seul qui ne soit pas admissible, car je ne l'avais pas spécifié dans mes options.
Remarquez dans le code que j'utilise à nouveau le nom d'attribut
propriété de l'objet MutationRecord
pour consigner l'attribut modifié. Et bien sûr, comme avec les autres démos, le MutationObserver
ne commencera pas à surveiller les modifications tant que le bouton "Démarrer" n'aura pas été cliqué.
Une autre chose que je devrais souligner ici est que je ne le fais pas. t pas besoin de définir les attributs
sur true
dans ce cas; il est implicite en raison de la définition de d'attributsFilter
. C’est pourquoi mon objet options pourrait se présenter comme suit et fonctionnerait de la même manière:
let options = {
attributFilter: ['hidden', 'contenteditable', 'data-par']
}
D'autre part, si j'assigne explicitement les attributs
à false
avec un tableau attributeFilter
cela ne fonctionnera pas, car le false La valeur
aurait priorité et l'option du filtre serait ignorée.
Observer les modifications apportées aux nœuds et à leur sous-arbre
Jusqu'à présent, lors de la configuration de chaque MutationObserver
traitant de l'élément ciblé lui-même et, dans le cas de childList
des enfants immédiats de l'élément. Mais il se peut que je veuille observer des modifications dans l’un des éléments suivants:
- Un élément et tous ses éléments enfants;
- Un ou plusieurs attributs d’un élément et de ses éléments enfants; [19659033] Tous les nœuds de texte à l'intérieur d'un élément.
Tout ce qui précède peut être réalisé à l'aide de la propriété subtree
de l'objet options.
childList With subtree
Commençons par rechercher les modifications apportées à un objet les nœuds enfants de l'élément, même s'ils ne sont pas des enfants immédiats. Je peux modifier mon objet options pour qu'il ressemble à ceci:
options = {
childList: true,
sous-arbre: true
}
Tout le reste du code est plus ou moins identique à l'exemple précédent childList
avec quelques balises et boutons supplémentaires.
Il existe deux listes, l'une imbriquée dans l'autre. Lorsque le MutationObserver
est démarré, le rappel déclenchera les modifications apportées à l'une ou l'autre liste. Mais si je devais redéfinir la propriété du sous-arbre
en false
(valeur par défaut lorsqu'elle n'est pas présente), le rappel ne s'exécuterait pas lorsque la liste imbriquée serait modifiée.
subtree
Voici un autre exemple, utilisant cette fois-ci sous-arbre
avec attributs
et attributFilter
. Cela me permet d’observer les modifications apportées aux attributs non seulement sur l’élément cible, mais également sur les attributs de tous les éléments enfants de l’élément cible:
options = {
attributs: vrai,
attributFilter: ['hidden', 'contenteditable', 'data-par'],
sous-arbre: true
}
Ceci est similaire à la démonstration d’attributs précédente, mais cette fois j’ai configuré deux éléments de sélection différents. La première modifie les attributs sur l'élément de paragraphe ciblé, tandis que l'autre modifie les attributs sur un élément enfant dans le paragraphe.
Encore une fois, si vous redéfinissiez l'option subtree
sur false
(ou le supprimer), le deuxième bouton bascule ne déclencherait pas le rappel de MutationObserver
. Et, bien sûr, je pourrais omettre complètement attributeFilter
et le MutationObserver
chercherait à modifier tous les attributs du sous-arbre plutôt que ceux spécifiés. ] characterData avec subtree
N'oubliez pas, dans la démonstration précédente de characterData
que le nœud ciblé avait disparu et que le MutationObserver
ne fonctionnait plus. Bien qu'il existe des moyens de contourner ce problème, il est plus facile de cibler directement un élément plutôt qu'un nœud de texte, puis utilisez la propriété subtree
pour indiquer que je souhaite que toutes les données de caractère contenues dans cet élément, quelle que soit leur profondeur. il est imbriqué, pour déclencher le rappel MutationObserver
.
Mes options dans ce cas seraient les suivantes:
options = {
characterData: true,
sous-arbre: true
}
Après avoir démarré l'observateur, essayez d'utiliser CTRL-B et CTRL-I pour formater le texte modifiable. Vous remarquerez que cela fonctionne beaucoup plus efficacement que l'exemple précédent characterData
. Dans ce cas, les nœuds enfants fragmentés n'affectent pas l'observateur car nous observons tous les nœuds à l'intérieur du nœud ciblé, au lieu d'un seul nœud de texte.
Enregistrement des anciennes valeurs
Souvent, lorsque vous observez des modifications de la DOM, vous voudrez prendre note des anciennes valeurs et éventuellement les stocker ou les utiliser ailleurs. Cela peut être fait en utilisant quelques propriétés différentes dans l'objet options
.
attributeOldValue
Tout d'abord, essayons de déconnecter l'ancienne valeur d'attribut après qu'elle a été modifiée. Voici à quoi ressembleront mes options et mon rappel:
options = {
attributs: vrai,
attributOldValue: true
}
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'attributs') {
// Faites quelque chose ici ...
}
}
}
Remarquez l'utilisation des propriétés attributeName
et oldValue
de l'objet MutationRecord
. Essayez la démo en entrant différentes valeurs dans le champ de texte. Notez que le journal est mis à jour pour refléter la valeur précédemment stockée.
characterDataOldValue
De la même façon, voici à quoi ressembleraient mes options si je veux enregistrer les anciennes données de caractère:
options = {
characterData: true,
sous-arbre: true,
characterDataOldValue: true
}
Notez que les messages du journal indiquent la valeur précédente. Les choses deviennent un peu négligées lorsque vous ajoutez du code HTML via des commandes de texte enrichi au mélange. Je ne sais pas quel est le comportement supposé correct dans ce cas, mais il est plus simple de choisir un seul nœud de texte à l'intérieur de l'élément.
Interception de mutations à l'aide de takeRecords ()
MutationObserver
objet que je n'ai pas encore mentionné est takeRecords ()
. Cette méthode vous permet d'intercepter plus ou moins les mutations détectées avant leur traitement par la fonction de rappel.
Je peux utiliser cette fonctionnalité à l'aide d'une ligne comme celle-ci:
let myRecords = observer.takeRecords ();
Ceci stocke une liste des modifications du DOM dans la variable spécifiée. Dans ma démo j’exécute cette commande dès que l’on clique sur le bouton qui modifie le DOM. Notez que les boutons de démarrage et d’ajout / suppression n’enregistrent rien. En effet, comme indiqué précédemment, j'intercepte les modifications du DOM avant leur traitement par le rappel.
Notez cependant ce que je fais dans l'écouteur d'événements qui arrête l'observateur:
btnStop.addEventListener ( 'click', function () {
observer.disconnect ();
si (mes enregistrements) {
console.log (`$ {myRecords [0] .target} a été modifié à l'aide de l'option $ {myRecords [0] .type}.);
}
}, faux);
Comme vous pouvez le constater, après avoir arrêté l'observateur à l'aide de observer.disconnect ()
j'accède à l'enregistrement de la mutation qui a été intercepté et je consigne l'élément cible ainsi que le type de mutation. cela a été enregistré. Si j'avais observé plusieurs types de modifications, l'enregistrement stocké contiendrait plusieurs éléments, chacun ayant son propre type.
Lorsqu'un enregistrement de mutation est intercepté de cette manière, en appelant takeRecords ()
la file d'attente des mutations qui seraient normalement envoyées à la fonction de rappel est vidée. Donc, si pour une raison quelconque vous devez intercepter ces enregistrements avant de les traiter, takeRecords ()
vous serait utile.
Observer plusieurs modifications à l'aide d'un seul observateur
Notez que si je ' En cherchant des mutations sur deux nœuds différents de la page, je peux le faire en utilisant le même observateur. Cela signifie qu'après avoir appelé le constructeur, je peux exécuter la méthode observe ()
pour autant d'éléments que je le souhaite.
Ainsi, après cette ligne:
observer = nouvelle MutationObserver (mCallback);
Je peux alors avoir plusieurs appels observe ()
avec différents éléments comme premier argument:
observer.observe (mList, options);
observateur.observer (mList2, options);
Démarrez l'observateur, puis essayez les boutons Ajouter / Supprimer des deux listes. Le seul problème ici est que, si vous appuyez sur l'un des boutons «Stop», l'observateur cessera d'observer les deux listes, pas seulement celle qu'il cible.
Déplacement d'un arbre de nœuds à observer
Une dernière chose que je Signalons que MutationObserver
continuera à observer les modifications apportées à un nœud spécifié même après que ce nœud ait été supprimé de son élément parent.
Par exemple, essayez la démo suivante: [19659003] Voici un autre exemple qui utilise childList
pour surveiller les modifications apportées aux éléments enfants d'un élément cible. Remarquez le bouton qui déconnecte la sous-liste, celle qui est observée. Cliquez sur le bouton “Démarrer…”, puis sur le bouton “Déplacer…” pour déplacer la liste imbriquée. Même après que la liste ait été supprimée de son parent, MutationObserver
continue à observer les modifications spécifiées. Cela n’est pas une surprise majeure, mais c’est quelque chose à garder à l’esprit.
Conclusion
Cela couvre à peu près toutes les principales caractéristiques de l’API MutationObserver
. J'espère que cette plongée en profondeur vous a été utile pour vous familiariser avec cette norme. Comme mentionné, le support du navigateur est puissant et vous pouvez en savoir plus sur cette API sur les pages de MDN .
J'ai mis toutes les démonstrations de cet article dans un CodePen collection si vous souhaitez avoir un endroit facile pour vous amuser avec les démos.
(dm, il)
19659006] Un MutationObserver
peut être utilisé de différentes manières, que je traiterai en détail dans la suite de cet article, mais la syntaxe de base pour un MutationObserver
ressemble à ceci : let observ = nouveau MutationObserver (rappel);
fonction callback (mutations) {
// fais quelque chose ici
}
observer.observer (targetNode, observerOptions);
La première ligne crée un nouveau MutationObserver
à l'aide du constructeur MutationObserver ()
. L'argument transmis au constructeur est une fonction de rappel qui sera appelée à chaque modification DOM qualifiée.
La manière de déterminer ce qui est qualifié pour un observateur particulier consiste à utiliser la dernière ligne du code ci-dessus. Sur cette ligne, j’utilise la méthode observe ()
de la MutationObserver
pour commencer à observer. Vous pouvez comparer cela à quelque chose comme addEventListener ()
. Dès que vous attachez un écouteur, la page "écoute" l’événement spécifié. De même, lorsque vous commencez à observer, la page commence à «observer» pour la MutationObserver
spécifiée.
La méthode observe ()
prend deux arguments: La cible ]qui devrait être le nœud ou l’arborescence de nœuds sur lequel observer les modifications; et un objet options qui est un objet MutationObserverInit
qui vous permet de définir la configuration pour l'observateur.
La dernière fonctionnalité de base essentielle d'un MutationObserver
. est la méthode disconnect ()
. Cela vous permet d’arrêter d’observer les modifications spécifiées et vous donne l’image suivante:
observer.disconnect ();
Options pour configurer un MutationObserver
Comme indiqué, la méthode observe ()
d'un MutationObserver
requiert un deuxième argument spécifiant les options permettant de décrire le MutationObserver .
. Voici à quoi ressemblerait l’objet options avec toutes les paires propriété / valeur possibles incluses:
let options = {
childList: true,
attributs: vrai,
characterData: false,
sous-arbre: faux,
attributFilter: ['one', 'two'],
attributOldValue: false,
characterDataOldValue: false
};
Lors de la configuration des options MutationObserver
il n’est pas nécessaire d’inclure toutes ces lignes. Je les inclut simplement à des fins de référence afin que vous puissiez voir quelles options sont disponibles et quels types de valeurs elles peuvent prendre. Comme vous pouvez le constater, tous sauf un sont booléens.
Pour qu'un MutationObserver
fonctionne, au moins un des childList
attributs
ou characterData
doit être défini sur true
sinon une erreur sera générée. Les quatre autres propriétés fonctionnent conjointement avec l’une de ces trois propriétés (voir plus loin).
Jusqu’à présent, j’ai simplement passé en revue la syntaxe pour vous donner un aperçu. Le meilleur moyen d’examiner le fonctionnement de chacune de ces fonctionnalités consiste à fournir des exemples de code et des démonstrations en direct intégrant les différentes options. C’est ce que je ferai pour le reste de cet article.
Observer les modifications apportées aux éléments enfants à l’aide de childList
La première et la plus simple MutationObserver
que vous pouvez initier est celle qui recherche les nœuds enfants d’un noeud spécifié (généralement un élément) à ajouter ou à supprimer. Pour mon exemple, je vais créer une liste non ordonnée dans mon code HTML et je veux savoir à chaque fois qu'un nœud enfant est ajouté ou supprimé de cet élément de la liste.
Le code HTML de la liste ressemble à ceci:
- Pommes
- Oranges
- Bananes
- Pêches
Le code JavaScript de mon MutationObserver
comprend les éléments suivants:
let mList = document.getElementById ('myList'),
options = {
childList: true
},
observateur = new MutationObserver (mCallback);
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'childList') {
console.log ('Mutation détectée: un noeud enfant a été ajouté ou supprimé.');
}
}
}
observateur.observer (liste, options);
Ceci n'est qu'une partie du code. Par souci de brièveté, je montre les sections les plus importantes qui traitent de l’API MutationObserver
elle-même.
Remarquez comment je passe en revue l’argument des mutations
qui est un . ] MutationRecord
objet qui possède plusieurs propriétés différentes. Dans ce cas, je lis la propriété de type
et enregistre un message indiquant que le navigateur a détecté une mutation qualifiée. Vous remarquerez également que je passe l'élément mList
(une référence à ma liste HTML) en tant qu'élément ciblé (c'est-à-dire l'élément sur lequel je souhaite observer des modifications).
Utilisez les boutons pour démarrez et arrêtez le MutationObserver
. Les messages du journal aident à clarifier ce qui se passe. Les commentaires dans le code fournissent également des explications.
Notez quelques points importants ici:
- La fonction de rappel (que j'ai nommée
mCallback
pour illustrer le fait que vous pouvez l'appeler comme vous voulez). se déclenche à chaque fois qu'une mutation réussie est détectée et après l'exécution de la méthode observe ()
- Dans mon exemple, le seul "type" de mutation qui se qualifie est
childList
. il est donc logique de chercher celui-ci lorsque vous parcourez MutationRecord. La recherche de tout autre type dans cet exemple ne ferait rien (les autres types seront utilisés dans les démos suivants).
- À l'aide de
childList
je peux ajouter ou supprimer un nœud de texte de l'élément ciblé, et cela également. serait admissible. Il n’est donc pas nécessaire que ce soit un élément ajouté ou supprimé.
- Dans cet exemple, seuls les noeuds enfant immédiats seront qualifiés. Nous verrons plus loin dans cet article comment cela peut s'appliquer à tous les nœuds enfants, petits-enfants, etc.
Observer les modifications apportées aux attributs d'un élément
Un autre type de mutation commun que vous pouvez suivre est le suivant: lorsqu'un attribut d'un élément spécifié change. Dans la prochaine démonstration interactive, je vais observer les modifications apportées aux attributs d’un élément de paragraphe.
laissez mPar = document.getElementById ('myParagraph'),
options = {
attributs: vrai
},
observateur = new MutationObserver (mCallback);
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'attributs') {
// Faites quelque chose ici ...
}
}
}
observateur.observer (mPar, options);
Encore une fois, j'ai abrégé le code pour plus de clarté, mais les parties importantes sont les suivantes:
- L'objet
options
utilise la propriété d'attributs
définie sur .
pour indiquer à MutationObserver
que je souhaite rechercher des modifications dans les attributs de l'élément ciblé.
- Le type de mutation que je teste dans ma boucle est
attributs
le
- J'utilise également la propriété
attributeName
de l'objet mutation
ce qui me permet de déterminer quel attribut a été modifié.
- Lorsque je déclenche l'observateur, je passe l'élément de paragraphe par référence, ainsi que les options.
Dans cet exemple, un bouton permet de basculer le nom d'une classe sur l'élément HTML ciblé. La fonction de rappel dans l'observateur de mutation est déclenchée chaque fois que la classe est ajoutée ou supprimée.
Observer les modifications apportées aux données de personnage
Vous pouvez également rechercher d'autres modifications dans votre application, à savoir les mutations des données de personnage; c'est-à-dire les modifications apportées à un nœud de texte spécifique. Pour ce faire, définissez la propriété characterData
sur true
dans l'objet options
. Voici le code:
let options = {
characterData: true
},
observateur = new MutationObserver (mCallback);
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'characterData') {
// Faites quelque chose ici ...
}
}
}
Remarquez à nouveau que le type
recherché dans la fonction de rappel est characterData
.
Dans cet exemple, je cherche à modifier un nœud de texte spécifique, que je target via element.childNodes [0]
. C'est un peu hacky mais cela conviendra pour cet exemple. Le texte peut être modifié par l'utilisateur via l'attribut contenteditable
d'un élément de paragraphe.
Défis lors de l'observation des modifications de données de caractères
Si vous avez manipulé de manière à ce qu'il soit modifiable
alors vous savez peut-être qu'il existe des raccourcis clavier permettant l'édition de texte enrichi. Par exemple, CTRL-B met le texte en gras, CTRL-I rend le texte en italique, etc. Cela divisera le nœud de texte en plusieurs nœuds de texte, vous remarquerez donc que le MutationObserver
cessera de répondre à moins que vous ne modifiiez le texte qui est toujours considéré comme faisant partie du nœud d'origine.
Je devrais également signaler. que si vous supprimez tout le texte, le MutationObserver
ne déclenchera plus le rappel. Je suppose que cela se produit car une fois que le nœud de texte a disparu, l’élément cible n’existe plus. Pour lutter contre cela, ma démo cesse d’observer lorsque le texte est supprimé, même si les raccourcis en texte enrichi deviennent un peu collants.
Mais ne vous inquiétez pas, dans la suite de cet article, je discuterai d’un meilleur moyen de utilisez l'option characterData
sans avoir à gérer autant de problèmes de ce genre.
Observer les modifications apportées aux attributs spécifiés
Je vous ai déjà montré comment observer les modifications apportées aux attributs d'un élément spécifié. Dans ce cas, bien que la démonstration déclenche un changement de nom de classe, j'aurais pu changer n'importe quel attribut de l'élément spécifié. Mais que faire si je veux observer des modifications dans un ou plusieurs attributs spécifiques tout en ignorant les autres?
Je peux le faire en utilisant la propriété facultative attributeFilter
dans l'objet option
. Voici un exemple:
let options = {
attributs: vrai,
attributFilter: ['hidden', 'contenteditable', 'data-par']
},
observateur = new MutationObserver (mCallback);
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'attributs') {
// Faites quelque chose ici ...
}
}
}
Comme indiqué ci-dessus, la propriété attributeFilter
accepte un tableau d'attributs spécifiques que je souhaite surveiller. Dans cet exemple, le MutationObserver
déclenchera le rappel à chaque fois qu'un ou plusieurs des attributs cachés
contenteditable
ou data-par
est modifié.
Encore une fois, je vise un élément de paragraphe spécifique. Notez le menu déroulant de sélection qui choisit l’attribut à modifier. L'attribut draggable
est le seul qui ne soit pas admissible, car je ne l'avais pas spécifié dans mes options.
Remarquez dans le code que j'utilise à nouveau le nom d'attribut
propriété de l'objet MutationRecord
pour consigner l'attribut modifié. Et bien sûr, comme avec les autres démos, le MutationObserver
ne commencera pas à surveiller les modifications tant que le bouton "Démarrer" n'aura pas été cliqué.
Une autre chose que je devrais souligner ici est que je ne le fais pas. t pas besoin de définir les attributs
sur true
dans ce cas; il est implicite en raison de la définition de d'attributsFilter
. C’est pourquoi mon objet options pourrait se présenter comme suit et fonctionnerait de la même manière:
let options = {
attributFilter: ['hidden', 'contenteditable', 'data-par']
}
D'autre part, si j'assigne explicitement les attributs
à false
avec un tableau attributeFilter
cela ne fonctionnera pas, car le false La valeur
aurait priorité et l'option du filtre serait ignorée.
Observer les modifications apportées aux nœuds et à leur sous-arbre
Jusqu'à présent, lors de la configuration de chaque MutationObserver
traitant de l'élément ciblé lui-même et, dans le cas de childList
des enfants immédiats de l'élément. Mais il se peut que je veuille observer des modifications dans l’un des éléments suivants:
- Un élément et tous ses éléments enfants;
- Un ou plusieurs attributs d’un élément et de ses éléments enfants; [19659033] Tous les nœuds de texte à l'intérieur d'un élément.
Tout ce qui précède peut être réalisé à l'aide de la propriété subtree
de l'objet options.
childList With subtree
Commençons par rechercher les modifications apportées à un objet les nœuds enfants de l'élément, même s'ils ne sont pas des enfants immédiats. Je peux modifier mon objet options pour qu'il ressemble à ceci:
options = {
childList: true,
sous-arbre: true
}
Tout le reste du code est plus ou moins identique à l'exemple précédent childList
avec quelques balises et boutons supplémentaires.
Il existe deux listes, l'une imbriquée dans l'autre. Lorsque le MutationObserver
est démarré, le rappel déclenchera les modifications apportées à l'une ou l'autre liste. Mais si je devais redéfinir la propriété du sous-arbre
en false
(valeur par défaut lorsqu'elle n'est pas présente), le rappel ne s'exécuterait pas lorsque la liste imbriquée serait modifiée.
subtree
Voici un autre exemple, utilisant cette fois-ci sous-arbre
avec attributs
et attributFilter
. Cela me permet d’observer les modifications apportées aux attributs non seulement sur l’élément cible, mais également sur les attributs de tous les éléments enfants de l’élément cible:
options = {
attributs: vrai,
attributFilter: ['hidden', 'contenteditable', 'data-par'],
sous-arbre: true
}
Ceci est similaire à la démonstration d’attributs précédente, mais cette fois j’ai configuré deux éléments de sélection différents. La première modifie les attributs sur l'élément de paragraphe ciblé, tandis que l'autre modifie les attributs sur un élément enfant dans le paragraphe.
Encore une fois, si vous redéfinissiez l'option subtree
sur false
(ou le supprimer), le deuxième bouton bascule ne déclencherait pas le rappel de MutationObserver
. Et, bien sûr, je pourrais omettre complètement attributeFilter
et le MutationObserver
chercherait à modifier tous les attributs du sous-arbre plutôt que ceux spécifiés. ] characterData avec subtree
N'oubliez pas, dans la démonstration précédente de characterData
que le nœud ciblé avait disparu et que le MutationObserver
ne fonctionnait plus. Bien qu'il existe des moyens de contourner ce problème, il est plus facile de cibler directement un élément plutôt qu'un nœud de texte, puis utilisez la propriété subtree
pour indiquer que je souhaite que toutes les données de caractère contenues dans cet élément, quelle que soit leur profondeur. il est imbriqué, pour déclencher le rappel MutationObserver
.
Mes options dans ce cas seraient les suivantes:
options = {
characterData: true,
sous-arbre: true
}
Après avoir démarré l'observateur, essayez d'utiliser CTRL-B et CTRL-I pour formater le texte modifiable. Vous remarquerez que cela fonctionne beaucoup plus efficacement que l'exemple précédent characterData
. Dans ce cas, les nœuds enfants fragmentés n'affectent pas l'observateur car nous observons tous les nœuds à l'intérieur du nœud ciblé, au lieu d'un seul nœud de texte.
Enregistrement des anciennes valeurs
Souvent, lorsque vous observez des modifications de la DOM, vous voudrez prendre note des anciennes valeurs et éventuellement les stocker ou les utiliser ailleurs. Cela peut être fait en utilisant quelques propriétés différentes dans l'objet options
.
attributeOldValue
Tout d'abord, essayons de déconnecter l'ancienne valeur d'attribut après qu'elle a été modifiée. Voici à quoi ressembleront mes options et mon rappel:
options = {
attributs: vrai,
attributOldValue: true
}
fonction mCallback (mutations) {
pour (laisser mutation de mutations) {
if (mutation.type === 'attributs') {
// Faites quelque chose ici ...
}
}
}
Remarquez l'utilisation des propriétés attributeName
et oldValue
de l'objet MutationRecord
. Essayez la démo en entrant différentes valeurs dans le champ de texte. Notez que le journal est mis à jour pour refléter la valeur précédemment stockée.
characterDataOldValue
De la même façon, voici à quoi ressembleraient mes options si je veux enregistrer les anciennes données de caractère:
options = {
characterData: true,
sous-arbre: true,
characterDataOldValue: true
}
Notez que les messages du journal indiquent la valeur précédente. Les choses deviennent un peu négligées lorsque vous ajoutez du code HTML via des commandes de texte enrichi au mélange. Je ne sais pas quel est le comportement supposé correct dans ce cas, mais il est plus simple de choisir un seul nœud de texte à l'intérieur de l'élément.
Interception de mutations à l'aide de takeRecords ()
MutationObserver
objet que je n'ai pas encore mentionné est takeRecords ()
. Cette méthode vous permet d'intercepter plus ou moins les mutations détectées avant leur traitement par la fonction de rappel.
Je peux utiliser cette fonctionnalité à l'aide d'une ligne comme celle-ci:
let myRecords = observer.takeRecords ();
Ceci stocke une liste des modifications du DOM dans la variable spécifiée. Dans ma démo j’exécute cette commande dès que l’on clique sur le bouton qui modifie le DOM. Notez que les boutons de démarrage et d’ajout / suppression n’enregistrent rien. En effet, comme indiqué précédemment, j'intercepte les modifications du DOM avant leur traitement par le rappel.
Notez cependant ce que je fais dans l'écouteur d'événements qui arrête l'observateur:
btnStop.addEventListener ( 'click', function () {
observer.disconnect ();
si (mes enregistrements) {
console.log (`$ {myRecords [0] .target} a été modifié à l'aide de l'option $ {myRecords [0] .type}.);
}
}, faux);
Comme vous pouvez le constater, après avoir arrêté l'observateur à l'aide de observer.disconnect ()
j'accède à l'enregistrement de la mutation qui a été intercepté et je consigne l'élément cible ainsi que le type de mutation. cela a été enregistré. Si j'avais observé plusieurs types de modifications, l'enregistrement stocké contiendrait plusieurs éléments, chacun ayant son propre type.
Lorsqu'un enregistrement de mutation est intercepté de cette manière, en appelant takeRecords ()
la file d'attente des mutations qui seraient normalement envoyées à la fonction de rappel est vidée. Donc, si pour une raison quelconque vous devez intercepter ces enregistrements avant de les traiter, takeRecords ()
vous serait utile.
Observer plusieurs modifications à l'aide d'un seul observateur
Notez que si je ' En cherchant des mutations sur deux nœuds différents de la page, je peux le faire en utilisant le même observateur. Cela signifie qu'après avoir appelé le constructeur, je peux exécuter la méthode observe ()
pour autant d'éléments que je le souhaite.
Ainsi, après cette ligne:
observer = nouvelle MutationObserver (mCallback);
Je peux alors avoir plusieurs appels observe ()
avec différents éléments comme premier argument:
observer.observe (mList, options);
observateur.observer (mList2, options);
Démarrez l'observateur, puis essayez les boutons Ajouter / Supprimer des deux listes. Le seul problème ici est que, si vous appuyez sur l'un des boutons «Stop», l'observateur cessera d'observer les deux listes, pas seulement celle qu'il cible.
Déplacement d'un arbre de nœuds à observer
Une dernière chose que je Signalons que MutationObserver
continuera à observer les modifications apportées à un nœud spécifié même après que ce nœud ait été supprimé de son élément parent.
Par exemple, essayez la démo suivante: [19659003] Voici un autre exemple qui utilise childList
pour surveiller les modifications apportées aux éléments enfants d'un élément cible. Remarquez le bouton qui déconnecte la sous-liste, celle qui est observée. Cliquez sur le bouton “Démarrer…”, puis sur le bouton “Déplacer…” pour déplacer la liste imbriquée. Même après que la liste ait été supprimée de son parent, MutationObserver
continue à observer les modifications spécifiées. Cela n’est pas une surprise majeure, mais c’est quelque chose à garder à l’esprit.
Conclusion
Cela couvre à peu près toutes les principales caractéristiques de l’API MutationObserver
. J'espère que cette plongée en profondeur vous a été utile pour vous familiariser avec cette norme. Comme mentionné, le support du navigateur est puissant et vous pouvez en savoir plus sur cette API sur les pages de MDN .
J'ai mis toutes les démonstrations de cet article dans un CodePen collection si vous souhaitez avoir un endroit facile pour vous amuser avec les démos.
(dm, il)
Source link