W3docs

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 :

DirectionPropriété (éléments uniquement)Propriété (tout inclus)
Vers le bas (enfants)children, firstElementChild, lastElementChildchildNodes, firstChild, lastChild
Vers le haut (parent)parentElementparentNode
Latérale (frères)nextElementSibling, previousElementSiblingnextSibling, 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.

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.

Info

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, nextSibling et childNodes comptent 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, nextElementSibling et leurs équivalents renvoient null aux limites de l'arbre (le dernier frère n'a pas de nextElementSibling). Vérifiez toujours avant d'appeler une méthode sur le résultat, comme le fait l'exemple de frère ci-dessus avec if (nextTask).
  • Traiter les collections comme des tableaux (arrays). children, childNodes et getElementsByClassName renvoient des collections, pas de vrais arrays. HTMLCollection n'a pas de forEach. Convertissez avec Array.from(collection) ou [...collection] lorsque vous avez besoin de méthodes de tableau comme map ou filter. (Le NodeList de querySelectorAll a bien forEach, mais pas map.)
  • Itérer sur une collection dynamique tout en la modifiant. Comme getElementsByClassName est dynamique, l'ajout ou la suppression d'éléments correspondants à l'intérieur d'une boucle for peut sauter des éléments ou boucler indéfiniment. Prenez d'abord un instantané avec Array.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.

Pratique

Pratique
Laquelle des méthodes suivantes peut être utilisée pour traverser le DOM en JavaScript ?
Laquelle des méthodes suivantes peut être utilisée pour traverser le DOM en JavaScript ?
Was this page helpful?