L’invasion du html
mutant

Slides en ligne
ffoodd.fr/devquest.2025/

Niveau 0
L’inspecteur

C’est notre héros du jour : invoquez-le avec F12.

L’inspecteur développe un site web, et tout se passe pour le mieux.

Mais au moment de faire un audit d’accessibilité, le DOM est sens dessus dessous et des scripts tiers font n’importe quoi !

Ni une, ni deux : l’inspecteur dégaine ses outils, il faut corriger le tir !

Niveau 0
Les outils

L’inspecteur n’a pas de flingue, il a une API !

Pour réparer le DOM, nous allons nous servir d’un MutationObserver.

Quelques règles :

  1. Il faut l’instancier.
  2. Il faut lui donner une cible.
  3. Il lui faut une fonction de rappel.
  4. Il faut le débrancher, à un moment.

Et maintenant, en avant !

const observer = new MutationObserver();
observer.observe(
	document.querySelector('#cible'),
	callback
);
observer.disconnect();
		

Niveau 1
Le zombie

Entrons dans le vif du sujet : les changements d’attribut

Avez-vous déjà eu besoin de déclencher quelque chose en JavaScript lorsqu’un élément change d’attribut ?

Songez à tous ces attributs qui peuvent être importants : hidden, aria-label, aria-invalid

Niveau 1
Le zombie

observer.observe(document.querySelector("mu-tant"), {
});

Niveau 2
Le super-vilain

Il n’y a pas que des gentils dans la vie.

Cas d’usage : les changements textuels

Avez-vous déjà eu besoin de déclencher quelque chose en JavaScript lorsque le contenu textuel d’un élement change ?

Imaginez un peu : un bouton dont l’intitulé change, mais pas d’état ni de classe auxquels s’accrocher ? Ou mieux, vous faites vos slides en HTML, avec une vue présentateur que vous voulez synchroniser sur votre pagination.

Niveau 2
Le super-vilain

observer.observe(document.querySelector("mu-tant"), {
});

Niveau 3
L’alien

Ils sont parmi nous !

Cas d’usage : les changements de descendance directe

Un contenu injecté inopinément — au hasard, lors d’un scroll infini, un script tiers qui injecte tout seul son DOM… — c’est irritant !

Ou encore : une iframe dont vous devriez ajuster la hauteur en fonction de son contenu…

Ceci n’est pas inspiré de faits réels, évidemment !

Niveau 3
L’alien

observer.observe(document.querySelector("mu-tant"), {
});

Niveau 4
Le fantôme

Vous n’avez pas entendu un truc ?

Cas d’usage : un attribut précis change pour une valeur spécifique

Imaginez un framework qui indique l’état invalide d’un champ de formulaire avec une classe, mais sans aria-invalid ? Vous savez comment patcher ça, maintenant.

Toute ressemblance avec des frameworks serait purement fortuite.

Niveau 4
Le fantôme

observer.observe(document.querySelector("mu-tant"), {
});

Niveau 5
Le sorcier

Il faut se méfier des types encapuchonnés
avec un grand bâton.

Cas d’usage : les changements de descendance profonde

Le saviez-vous ? Un Web Component ne peut pas émettre un événement en disparaissant… Donc si vous voulez déclencher quelque chose, vous saurez comment faire !

Toute ressemblance avec des frameworks serait purement fortuite.

Niveau 5
Le sorcier

observer.observe(document.querySelector("mu-tant"), {
});

Niveau 6
Le vampire

Il faut aussi se méfier des types avec un sourire qui déborde.

Cas d’usage : les valeurs disparues

Vous avez connu l’époque des classes .open et .show pour afficher ou masquer un élément — et aucun autre changement dans le DOM ? Ou un script tiers qui fait des trucs sans émettre d’événements ?

Bien entendu, ce sujet est une pure fiction.

Niveau 6
Le vampire

const slayer = new MutationObserver(mutations => {
	for (const mutation of mutations) {
		if (										) {
			mutation.target.remove();
			slayer.disconnect();
		}
	}
});
slayer.observe(document.querySelector("mu-tant"), {
});

Niveau 7
Le vieillissement

Vous ne l’aviez pas vu venir, hein ?

Cas d’usage : les caractères disparus

Un composant de création de mot de passe qui enlève la description des contraintes franchies…

Vous n’avez jamais vu ça ?

Niveau 7
Le vieillissement

const death = new MutationObserver(mutations => {
	for (const mutation of mutations) {
		if (									) {
			mutation.target.remove();
			death.disconnect();
		}
	}
});
death.observe(document.querySelector("mu-tant"), {
});

Niveau 8
Le boss final

C’est le moment de sauver des vies.

La fonction de rappel dispose aussi de pouvoirs.

Jusqu’à présent, j’ai savamment supprimé l’élément mutant : mais comment faire si on veut trier, filtrer… bref, tenir compte du contexte ?

Niveau 8
Le boss final

const death = new MutationObserver(mutations => {
	for (const mutation of mutations) {
	}
});

Niveau 8
Les solutions

Le bazooka

document.querySelectorAll('mu-tant:not([type=""])')
	.forEach(mutant => mutant.remove());

Le précautionneux

if (mutation.target.nodeName === 'MU-TANT' && mutation.target.type !== '') {
	mutation.target.remove();
} else if (mutation.target.closest('mu-tant') !== undefined) {
	mutation.target.closest('mu-tant').remove();
}

Malgré toutes les options, on peut avoir du mal à trier les mutations. Fort à propos, le type MutationRecord dispose de tout un tas de propriétés bien utiles.

Niveau bonus
Mais encore ?

Les MutationObserver ne sont pas seuls !

Générique de fin
Merci

Découvrez le jeu réalisé pour le dernier devFest Nantes !

Crédits

Laissez votre feedback

Openfeedback