Parcourir le DOM avec JavaScript
Parcourir le DOM est une compétence fondamentale pour les développeurs web. Maîtriser la traversée du DOM vous permettra de manipuler les pages web dynamiquement.
Parcourir le DOM (Document Object Model) est une compétence fondamentale pour les développeurs web qui utilisent JavaScript. Maîtriser la traversée du DOM vous permettra de manipuler les pages web dynamiquement, en créant des expériences utilisateur interactives et réactives. Ce guide vous fournira des explications détaillées et de nombreux exemples de code pour vous aider à devenir compétent dans la traversée du DOM.
Introduction à la traversée du DOM
Le DOM représente la structure d'une page web sous forme d'arbre de nœuds. Chaque nœud correspond à un élément, un morceau de texte ou un commentaire sur la page. Traverser le DOM signifie se déplacer d'un nœud à un autre — vers le haut vers un parent, vers le bas vers des enfants, ou latéralement vers des frères — pour lire ou modifier des éléments par rapport à un point de départ.
Pourquoi traverser plutôt que simplement sélectionner ? On part souvent d'un élément qu'on possède déjà (par exemple, le bouton sur lequel l'utilisateur vient de cliquer) et on a besoin d'atteindre un élément associé dont on ne connaît pas à l'avance l'id exact ou la classe — son conteneur, l'élément suivant dans une liste, ou chaque réponse imbriquée en dessous. La traversée exprime « l'élément à côté de / à l'intérieur de / autour de celui-ci. »
Ce guide couvre les propriétés de relation que vous utiliserez le plus souvent :
| Direction | Propriété (éléments uniquement) | Propriété (tout inclus) |
|---|---|---|
| Vers le bas (enfants) | children, firstElementChild, lastElementChild | childNodes, firstChild, lastChild |
| Vers le haut (parent) | parentElement | parentNode |
| Latérale (frères) | nextElementSibling, previousElementSibling | nextSibling, previousSibling |
La colonne de gauche ignore les nœuds de texte et de commentaire, ce qui correspond presque toujours à ce que vous souhaitez. La colonne de droite inclut les nœuds de texte d'espacement entre les balises, ce qui est une source courante de bogues.
Pour trouver des éléments n'importe où dans le document (plutôt que par rapport à un nœud), consultez Recherche : getElement* et querySelector et Sélectionner des éléments DOM.
Comprendre l'arbre DOM
Avant de plonger dans les méthodes de traversée, il est utile de visualiser l'arbre DOM. Voici un document HTML simple pour illustrer :
<!DOCTYPE html>
<html>
<head>
<title>DOM Traversal Example</title>
</head>
<body>
<div id="container">
<p class="text">Hello, World!</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
</body>
</html>Dans ce document, l'élément <body> contient un <div> avec un id de container, qui contient à son tour un élément <p> et un <ul> avec des enfants <li>. Pour apprendre comment le navigateur classe chaque élément (élément, texte, commentaire), consultez Comprendre les nœuds du DOM.
Méthodes de traversée de base
Accéder aux nœuds enfants
Imaginez que vous avez un blog avec plusieurs articles, et que chaque article a des commentaires. Vous souhaitez compter les commentaires d'un article spécifique.
<!DOCTYPE html>
<html>
<head>
<title>Accessing Child Nodes</title>
</head>
<body>
<div id="blog-post">
<h2>Blog Post Title</h2>
<p>Some interesting content...</p>
<div class="comments">
<p>Comment 1</p>
<p>Comment 2</p>
<p>Comment 3</p>
</div>
</div>
<script>
const commentsContainer = document.querySelector('.comments');
const comments = commentsContainer.children; // Only includes element nodes
// Display the number of comments
console.log(`Number of comments: ${comments.length}`);
</script>
</body>
</html>Ce code sélectionne le <div> avec la classe « comments » et affiche le nombre d'éléments de commentaire qu'il contient. Remarque : children renvoie uniquement les nœuds d'élément, tandis que childNodes inclut les nœuds de texte et de commentaire. Pour une traversée limitée aux éléments, préférez children.
Navigation vers les nœuds parents
Imaginez que vous avez une liste d'articles dans un panier d'achat et que vous souhaitez trouver l'élément conteneur d'un article spécifique.
<!DOCTYPE html>
<html>
<head>
<title>Navigating to Parent Nodes</title>
</head>
<body>
<div id="shopping-cart">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
<script>
const cartItem = document.querySelector('li');
const parent = cartItem.parentNode;
// Display the parent node
console.log(`The parent of the first cart item is a: ${parent.tagName}`);
</script>
</body>
</html>Ce code sélectionne le premier élément <li> et affiche le nom de balise de son nœud parent. Pour une navigation limitée aux éléments, parentElement est souvent préféré à parentNode car il ignore les nœuds de texte et renvoie null si le parent n'est pas un élément.
Nœuds frères
Imaginez que vous avez une liste de tâches où vous pouvez marquer des tâches comme terminées, puis passer à la tâche suivante.
<!DOCTYPE html>
<html>
<head>
<title>Task List Navigation</title>
<style>
.task {
margin: 10px;
padding: 10px;
border: 1px solid #ccc;
}
.completed {
text-decoration: line-through;
color: gray;
}
</style>
</head>
<body>
<div class="task-list">
<div class="task">
<p>Task 1: Do the laundry</p>
<button class="complete-task">Complete Task</button>
</div>
<div class="task">
<p>Task 2: Buy groceries</p>
<button class="complete-task">Complete Task</button>
</div>
<div class="task">
<p>Task 3: Clean the house</p>
<button class="complete-task">Complete Task</button>
</div>
</div>
<script>
document.querySelectorAll('.complete-task').forEach(button => {
button.addEventListener('click', () => {
const task = button.parentElement;
task.classList.add('completed');
button.disabled = true;
const nextTask = task.nextElementSibling;
if (nextTask) {
console.log(`Next task: ${nextTask.querySelector('p').textContent}`);
} else {
console.log('No more tasks available');
}
});
});
</script>
</body>
</html>Ce code fournit une liste de tâches où chaque tâche a un bouton « Complete Task ». Lorsqu'une tâche est marquée comme terminée, le texte est barré et le bouton est désactivé. Il affiche également la description de la tâche suivante. S'il n'y a plus de tâches, il indique qu'aucune autre tâche n'est disponible. De même, previousElementSibling et nextElementSibling ignorent les nœuds de texte, ce qui les rend plus sûrs pour une traversée limitée aux éléments que previousSibling et nextSibling.
Techniques de traversée avancées
Trouver des éléments par classe ou balise
Imaginez que vous créez un tableau de bord répertoriant tous les utilisateurs et que vous souhaitez trouver et compter tous les éléments utilisateur.
<!DOCTYPE html>
<html>
<head>
<title>Finding Elements by Class or Tag</title>
</head>
<body>
<div class="user">User 1</div>
<div class="user">User 2</div>
<div class="user">User 3</div>
<script>
const users = document.getElementsByClassName('user');
// Display the number of users
console.log(`Number of users: ${users.length}`);
</script>
</body>
</html>Ce code compte et affiche le nombre d'éléments avec la classe user. Un détail subtil mais important : getElementsByClassName (et getElementsByTagName) renvoie un HTMLCollection dynamique — il se met à jour automatiquement lorsque le DOM change. Si vous ajoutez ultérieurement un quatrième élément .user, users.length devient 4 sans nouvelle requête. En revanche, querySelectorAll renvoie un NodeList statique qui est un instantané pris au moment de l'appel. Comparez les deux dans Recherche : getElement* et querySelector.
Méthodes querySelector
Imaginez que vous avez un site d'informations et que vous souhaitez mettre en évidence tous les titres.
<!DOCTYPE html>
<html>
<head>
<title>Query Selector Methods</title>
</head>
<body>
<div id="news">
<h1 class="headline">Headline 1</h1>
<h1 class="headline">Headline 2</h1>
<h1 class="headline">Headline 3</h1>
</div>
<script>
const headlines = document.querySelectorAll('.headline');
// Highlight all headlines
headlines.forEach(headline => {
headline.style.color = 'red';
});
// Display the number of headlines
console.log(`Number of headlines: ${headlines.length}`);
</script>
</body>
</html>Ce code sélectionne tous les éléments avec la classe headline, change leur couleur en rouge et affiche le nombre de ces éléments.
Traversée avec des fonctions récursives
Créons un exemple concret de traversée récursive. Nous utiliserons un système de commentaires imbriqués comme exemple, où chaque commentaire peut avoir des réponses.
<!DOCTYPE html>
<html>
<head>
<title>Recursive Traversal</title>
<style>
.comment {
margin: 10px;
padding: 10px;
border: 1px solid #ccc;
}
.reply {
margin-left: 20px;
border-left: 2px solid #aaa;
}
</style>
</head>
<body>
<div class="comments">
<div class="comment">
<p>Comment 1</p>
<div class="reply">
<p>Reply 1-1</p>
<div class="reply">
<p>Reply 1-1-1</p>
</div>
</div>
<div class="reply">
<p>Reply 1-2</p>
</div>
</div>
<div class="comment">
<p>Comment 2</p>
<div class="reply">
<p>Reply 2-1</p>
</div>
</div>
</div>
<script>
function traverseComments(node) {
if (!node) return; // Guard against null/undefined
if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('comment')) {
console.log(`Comment: ${node.querySelector('p').textContent}`);
}
if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('reply')) {
console.log(`Reply: ${node.querySelector('p').textContent}`);
}
for (let i = 0; i < node.childNodes.length; i++) {
traverseComments(node.childNodes[i]);
}
}
traverseComments(document.querySelector('.comments'));
</script>
</body>
</html>Ce code représente un système de commentaires imbriqués avec des commentaires et des réponses. La fonction traverseComments parcourt récursivement chaque commentaire et chaque réponse en affichant leur contenu textuel. La structure imbriquée permet des réponses aux réponses, illustrant un cas d'utilisation concret de la traversée récursive. Incluez toujours une vérification null/undefined au début des fonctions récursives pour éviter les erreurs lorsque le sélecteur initial ne renvoie rien.
Exemples pratiques
Ces exemples combinent la traversée du DOM avec des techniques de manipulation courantes pour illustrer des flux de travail réels.
Créer une liste de tâches dynamique
Imaginez que vous avez une liste de tâches où les utilisateurs peuvent ajouter de nouvelles tâches.
<!DOCTYPE html>
<html>
<head>
<title>Dynamic To-Do List</title>
<style>
.info { color: darkgreen; }
</style>
</head>
<body>
<div id="todo-list">
<h2>To-Do List</h2>
<ul id="tasks">
<li>Task 1</li>
<li>Task 2</li>
</ul>
<input type="text" id="task-input" placeholder="Add a new task" />
<button id="add-button">Add Task</button>
</div>
<script>
const tasks = document.getElementById('tasks');
const input = document.getElementById('task-input');
const button = document.getElementById('add-button');
button.addEventListener('click', () => {
const newTask = input.value.trim();
if (newTask) {
const li = document.createElement('li');
li.textContent = newTask;
tasks.appendChild(li);
input.value = '';
console.log('Added new task to the to-do list');
}
});
</script>
</body>
</html>Ce code permet aux utilisateurs d'ajouter de nouvelles tâches à une liste en saisissant du texte dans un champ de saisie et en cliquant sur un bouton.
Mettre à jour les attributs d'un élément
Imaginez que vous avez une liste de produits et que vous souhaitez marquer des produits comme « favoris » lorsqu'ils sont cliqués.
<!DOCTYPE html>
<html>
<head>
<title>Updating Element Attributes</title>
<style>
.favorite { font-weight: bold; color: gold; }
.info { color: darkblue; }
</style>
</head>
<body>
<h4>Click on the list item below to see the result!</h4>
<ul id="product-list">
<li>Product 1</li>
<li>Product 2</li>
<li>Product 3</li>
</ul>
<script>
const productList = document.getElementById('product-list');
productList.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
event.target.classList.toggle('favorite');
console.log(`Toggled favorite status for: ${event.target.textContent}`);
}
});
</script>
</body>
</html>Ce code permet aux utilisateurs de marquer des produits comme « favoris » en cliquant dessus, ce qui modifie leur apparence grâce à une classe favorite.
Minimisez les accès au DOM pour améliorer les performances. Regroupez les manipulations du DOM pour réduire les reflows et les repaints.
Pièges courants
Quelques pièges sont à l'origine de la plupart des bogues liés à la traversée du DOM :
- Nœuds de texte d'espacement.
firstChild,nextSiblingetchildNodescomptent les espaces et les sauts de ligne entre les balises comme des nœuds de texte. Le premier enfant d'un<ul>écrit sur plusieurs lignes est généralement un nœud de texte, pas le premier<li>. Utilisez les versions limitées aux éléments (firstElementChild,nextElementSibling,children) sauf si vous avez spécifiquement besoin des nœuds de texte. - Oublier que la traversée peut renvoyer
null.parentElement,nextElementSiblinget leurs équivalents renvoientnullaux limites de l'arbre (le dernier frère n'a pas denextElementSibling). Vérifiez toujours avant d'appeler une méthode sur le résultat, comme le fait l'exemple de frère ci-dessus avecif (nextTask). - Traiter les collections comme des tableaux (arrays).
children,childNodesetgetElementsByClassNamerenvoient des collections, pas de vrais arrays.HTMLCollectionn'a pas deforEach. Convertissez avecArray.from(collection)ou[...collection]lorsque vous avez besoin de méthodes de tableau commemapoufilter. (LeNodeListdequerySelectorAlla bienforEach, mais pasmap.) - Itérer sur une collection dynamique tout en la modifiant. Comme
getElementsByClassNameest dynamique, l'ajout ou la suppression d'éléments correspondants à l'intérieur d'une boucleforpeut sauter des éléments ou boucler indéfiniment. Prenez d'abord un instantané avecArray.from(...)si vous prévoyez de muter pendant l'itération.
Pour approfondir la façon dont les nœuds sont typés et à quoi ressemble leur contenu, consultez Propriétés des nœuds : type, balise et contenu. Pour répondre aux actions de l'utilisateur lors de la traversée, consultez Gestion des événements dans le DOM.
Conclusion
Maîtriser la traversée du DOM est essentiel pour créer des applications web dynamiques et interactives. En comprenant et en utilisant les différentes méthodes et techniques de navigation et de manipulation du DOM, vous pouvez améliorer l'expérience utilisateur et augmenter les fonctionnalités de vos projets web.