Les bases du DOM
Qu’est-ce que le DOM ?
Section intitulée « Qu’est-ce que le DOM ? »Quand un navigateur charge une page HTML, il n’en conserve pas le texte tel quel. Il le lit et en construit une représentation en mémoire : une structure d’objets qu’un script JavaScript chargé sur la page peut lire et modifier. Cette structure est le DOM.
Une fois la page chargée, on ne manipule plus le HTML mais le DOM. Cette distinction explique qu’une page puisse changer sans être rechargée : modifier le DOM met aussitôt à jour ce que voit l’utilisateur.
L’arbre du document
Section intitulée « L’arbre du document »Le terme « arbre » désigne une structure précise : chaque élément (HTMLElement) a un parent et peut avoir des enfants,
et l’ensemble descend d’une racine unique (<html>), comme une arborescence de fichiers descend d’un dossier racine.
L’exemple ci-dessous montre une carte affichant une boîte à livres. À gauche, ce que voit l’utilisateur ; à droite, l’arbre DOM correspondant. Survoler un élément d’un côté illumine son jumeau de l’autre ; cliquer sur un nœud affiche ses détails.
Cet inspecteur reproduit ce que fait l’onglet Elements (ou Inspecteur) des outils de développement du navigateur. Chaque chose visible à l’écran correspond à un nœud de l’arbre, et inversement. Savoir circuler entre ces deux vues est une compétence de base du développement web.
Vocabulaire de l’arbre
Section intitulée « Vocabulaire de l’arbre »- Nœud (node) : tout élément de l’arbre, une balise, mais aussi le texte qu’elle contient ou un commentaire.
- Élément (element) : un nœud qui est une balise HTML (
<p>,<div>,<a>). - Parent : le nœud qui en contient directement un autre.
- Enfant (child) : un nœud contenu directement dans un autre.
- Frères (siblings) : des nœuds qui partagent le même parent.
- Racine (root) : le sommet de l’arbre, l’élément
<html>.
VérificationDans la carte ci-dessus, quel est le parent des trois <li> ? Et leurs frères ?
Le parent des trois <li> est le <ul> (la liste des tags). Les trois <li> sont frères entre eux,
car ils partagent le même parent. Le <ul> est l’enfant de <article> et le frère de <h2>, <p> et <a>.
Sélectionner des éléments
Section intitulée « Sélectionner des éléments »Pour modifier un élément, il faut d’abord l’atteindre depuis le JavaScript. Le DOM fournit des méthodes de sélection. Les deux méthodes modernes, employées presque toujours, prennent un sélecteur CSS :
// Le premier élément qui correspond au sélecteur CSSconst titre = document.querySelector(".carte__titre");
// Tous les éléments qui correspondent (une NodeList)const tags = document.querySelectorAll(".carte__tags li");Des méthodes plus anciennes restent répandues dans le code existant :
document.getElementById("menu"); // par id (sans le #)document.getElementsByClassName("carte"); // par classe (collection live)document.getElementsByTagName("li"); // par baliseDans du code récent, on préfère querySelector et querySelectorAll :
une seule syntaxe à retenir, identique à celle de CSS.
querySelector vs querySelectorAll
Section intitulée « querySelector vs querySelectorAll »| Retourne | Si rien ne correspond | |
|---|---|---|
querySelector(sel) |
le premier élément (ou null) |
null |
querySelectorAll(sel) |
tous les éléments (une NodeList) |
NodeList vide |
Une NodeList ressemble à un tableau et s’itère avec for...of ou forEach,
sans en être tout à fait un. Pour utiliser map ou filter, on la convertit d’abord avec Array.from(tags) ou [...tags].
Lire et modifier un élément
Section intitulée « Lire et modifier un élément »Une fois un élément sélectionné, on accède à ses propriétés comme à celles de n’importe quel objet JavaScript.
Le contenu
Section intitulée « Le contenu »const titre = document.querySelector(".carte__titre");
titre.textContent = "Nouvelle boîte"; // remplace le textetitre.textContent; // lit le texte actuelLes attributs
Section intitulée « Les attributs »const lien = document.querySelector(".carte__lien");
lien.getAttribute("href"); // lire un attributlien.setAttribute("href", "/boites/7"); // modifier un attributlien.href = "/boites/7"; // raccourci pour les attributs courantsLes classes CSS
Section intitulée « Les classes CSS »La propriété classList est la façon idiomatique de manipuler les classes, donc l’apparence :
const carte = document.querySelector(".carte");
carte.classList.add("active"); // ajouter une classecarte.classList.remove("active"); // retirercarte.classList.toggle("active"); // basculer (ajoute si absente, retire si présente)carte.classList.contains("active"); // tester (true / false)Modifier les classes plutôt que les styles directement garde la logique d’apparence dans le CSS. JavaScript décide quand une classe s’applique, CSS décide à quoi elle ressemble.
Créer et insérer des éléments
Section intitulée « Créer et insérer des éléments »On peut fabriquer de nouveaux nœuds et les greffer dans l’arbre :
// 1. Créer l'élémentconst nouveauTag = document.createElement("li");nouveauTag.textContent = "poésie";
// 2. L'insérer dans le DOMconst liste = document.querySelector(".carte__tags");liste.appendChild(nouveauTag); // ajoute à la fin des enfantsTant qu’un élément créé n’est pas inséré dans l’arbre, il existe en mémoire mais
n’apparaît nulle part à l’écran : il reste détaché.
C’est l’insertion (appendChild, prepend, before, after) qui le rend visible.
Pour retirer un élément :
nouveauTag.remove(); // se retire lui-même de l'arbreVérificationOn a fait document.createElement('p') et défini son textContent, mais rien n'apparaît. Pourquoi ?
Parce que l’élément a été créé en mémoire mais jamais inséré dans l’arbre du document.
Un élément créé est détaché : il faut l’attacher à un parent déjà présent dans le DOM avec
appendChild, prepend, before ou after pour qu’il devienne visible. Créer n’est pas insérer.
Gestion des événements
Section intitulée « Gestion des événements »Réagir aux événements
Section intitulée « Réagir aux événements »Le DOM ne sert pas qu’à modifier la page : il permet aussi de réagir à ce que fait l’utilisateur, qu’il s’agisse d’un clic, d’une frappe au clavier, d’un survol ou de la soumission d’un formulaire. On attache pour cela un écouteur d’événement (event listener) à un élément.
const bouton = document.querySelector(".carte__lien");
bouton.addEventListener("click", (evenement) => { evenement.preventDefault(); // empêche le comportement par défaut console.log("La boîte a été cliquée");});addEventListener prend deux arguments : le type d’événement ("click", "input", "submit", "mouseover"…)
et une fonction exécutée quand il survient. Cette fonction reçoit un objet evenement décrivant ce qui s’est produit.
Les pièces précédentes se combinent ici. Une page interactive forme une boucle : l’utilisateur agit, un événement se déclenche, JavaScript modifie le DOM, et l’affichage change.
// Exemple complet : un compteur de clicsconst bouton = document.querySelector("#compteur");let clics = 0;
bouton.addEventListener("click", () => { clics++; bouton.textContent = `Cliqué ${clics} fois`;});VérificationÀ quoi sert evenement.preventDefault() dans le clic sur un lien <a> ?
Un lien <a href="..."> a un comportement par défaut : naviguer vers l’URL. preventDefault()
annule ce comportement, ce qui permet d’exécuter du JavaScript à la place (par exemple afficher du contenu sans changer de page).
Sans cet appel, le navigateur suivrait le lien et quitterait la page avant l’exécution du reste du code.
Le cycle d’un événement
Section intitulée « Le cycle d’un événement »Lorsqu’un utilisateur clique sur un élément, le navigateur ne transmet pas simplement l’événement à cet élément isolé. Comme les éléments HTML sont imbriqués dans l’arbre du DOM, l’événement est aussi relié aux éléments parents : une image peut être dans un bouton, lui-même dans une carte, elle-même dans une section.
Pour gérer correctement les interactions, il faut donc comprendre le trajet d’un événement dans l’arbre du document. Ce trajet se déroule en trois phases.
- Capture : l’événement descend de la racine jusqu’à l’élément cliqué.
- Cible : il atteint l’élément effectivement cliqué.
- Bouillonnement : il remonte de la cible vers la racine.
Par défaut, vos écouteurs réagissent à la phase de bouillonnement. Pour écouter en capture, on passe une option à addEventListener.
event.target est l’élément réellement cliqué,
event.currentTarget est l’élément dont l’écouteur s’exécute en ce moment.
Manipulez la séquence ci-dessous : activez la capture, ou coupez la remontée au parent, et observez l’ordre de déclenchement.
- en attente d'un clic…
La capture descend, du grand-parent vers la cible ; le bouillonnement remonte, de la cible vers le grand-parent. event.target reste l'élément cliqué ; event.currentTarget est celui qui traite l'événement à cet instant.
// écouter en phase de capture (sinon, par défaut, c'est le bouillonnement)zone.addEventListener("click", gerer, { capture: true });
enfant.addEventListener("click", (e) => { console.log(e.target); // l'élément cliqué console.log(e.currentTarget); // l'élément qui écoute (ici, enfant) e.stopPropagation(); // stoppe la suite de la propagation});
// annuler l'action par défaut (navigation d'un lien, envoi d'un formulaire)lien.addEventListener("click", (e) => e.preventDefault());stopPropagation() arrête le voyage de l’événement ; preventDefault() n’arrête pas le voyage,
mais annule l’action que le navigateur ferait par défaut. Ce sont deux choses distinctes.
VérificationVous cliquez sur un bouton imbriqué dans une carte. Dans le gestionnaire posé sur la carte, que valent `event.target` et `event.currentTarget` ?
event.target est le bouton (l’élément réellement cliqué). event.currentTarget est la
carte (l’élément dont l’écouteur s’exécute). C’est précisément cette distinction qui rend la délégation possible.
La délégation d’événements
Section intitulée « La délégation d’événements »Comme l’événement remonte par bouillonnement, on peut placer un seul écouteur
sur un parent plutôt qu’un par enfant. Le parent inspecte event.target pour savoir d’où vient le clic.
On retrouve l’élément utile avec closest().
Supposons qu’une page affiche une liste de livres. Une première approche consisterait à ajouter
un écouteur click sur chaque élément <li>. Cette approche fonctionne, mais elle devient vite répétitive.
Elle pose aussi un problème lorsque de nouveaux livres sont ajoutés dynamiquement : il faut penser à leur attacher de nouveaux écouteurs.
La délégation d’événements consiste à déplacer l’écouteur vers un élément parent stable, par exemple la liste complète. Le parent reçoit les clics qui remontent par bouillonnement et détermine ensuite quel élément enfant a été visé.
const liste = document.querySelector("#livres");
liste.addEventListener("click", (e) => { const item = e.target.closest("li"); // remonte jusqu'au <li> le plus proche if (!item) return; // clic hors d'un item : on ignore console.log("livre choisi :", item.dataset.id);});Les templates
Section intitulée « Les templates »Lorsqu’une même structure HTML doit être répétée plusieurs fois, par exemple pour afficher une liste de cartes,
il peut être tentant de construire une longue chaîne de caractères HTML.
Cette approche est fragile : une erreur de syntaxe est difficile à repérer, et l’insertion de contenu utilisateur
avec innerHTML peut introduire des risques de sécurité.
L’élément <template> permet de conserver la structure HTML dans le document, puis de la cloner en JavaScript lorsque nécessaire.
Il est analysé mais n’est pas rendu : ses images et scripts ne se chargent pas, tant qu’on ne le clone pas.
C’est l’outil idéal pour répéter une structure.
<template id="tpl-livre"> <li class="livre"> <span class="titre"></span> <span class="auteur"></span> </li></template>const tpl = document.querySelector("#tpl-livre");const liste = document.querySelector("#livres");
for (const livre of livres) { const noeud = tpl.content.cloneNode(true); // copie profonde du contenu noeud.querySelector(".titre").textContent = livre.titre; noeud.querySelector(".auteur").textContent = livre.auteur; liste.append(noeud);}textContent, jamais en concaténant du HTML : le contenu reste du texte, ce qui évite l’injection. Comparé à innerHTML, le template garde la structure au même endroit et ne force pas le navigateur à ré-analyser une chaîne à chaque insertion.