Fermer

avril 26, 2019

Connaître l'API MutationObserver


Il est parfois nécessaire de surveiller les modifications apportées au DOM dans les applications et les infrastructures Web complexes. À l'aide d'explications et de démonstrations interactives, cet article vous montrera comment utiliser l'API MutationObserver pour faciliter l'observation des modifications du DOM.

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.

 Editorial Smashing (dm, il)




Source link