Optimisation des performances dans le développement web
L'optimisation des performances est essentielle en développement web pour créer des applications rapides et efficaces. Ce guide couvre les techniques d'optimisation du DOM.
Le DOM est l'élément de loin le plus coûteux que la plupart des applications JavaScript manipulent. Lire une propriété comme offsetHeight peut forcer le navigateur à s'arrêter et à recalculer la géométrie de la page, tandis qu'écrire dans le DOM peut déclencher un reflow (recalcul des positions et tailles des éléments) et un repaint (redessin des pixels). Faire cela sans précaution à l'intérieur d'une boucle peut rendre une page qui devrait être instantanée saccadée.
Ce guide explique pourquoi les opérations sur le DOM sont lentes, puis présente des techniques concrètes pour y remédier : minimiser les accès, éviter le layout thrashing, regrouper les modifications avec requestAnimationFrame et document.createDocumentFragment(), et profiler ce que l'on ne peut pas deviner.
Pourquoi les opérations DOM sont-elles lentes ?
JavaScript s'exécute dans un moteur rapide, mais le DOM est la frontière entre ce moteur et le pipeline de rendu du navigateur. C'est le franchissement répété de cette frontière qui pose problème :
- Reflow (mise en page) — le navigateur recalcule la position et la taille des éléments. Un reflow sur un élément peut se propager à ses ancêtres, descendants et frères.
- Repaint — le navigateur redessine les pixels (couleurs, ombres, visibilité) sans modifier la géométrie. Moins coûteux qu'un reflow, mais pas gratuit pour autant.
L'idée clé : le navigateur tente de regrouper ces opérations pour vous. Il met vos écritures en file d'attente et les exécute en une seule fois, juste avant que la prochaine image soit affichée. Vous brisez cette optimisation dès que vous lisez une propriété de mise en page, car le navigateur doit immédiatement vider toutes les écritures en attente pour vous fournir une réponse exacte. Ce vidage forcé est appelé layout synchrone (forcé), et le provoquer en boucle est à l'origine de la plupart des problèmes de performance DOM.
Minimiser les accès au DOM
Chaque lecture et écriture de propriété franchit la frontière JS-vers-DOM, donc l'optimisation la moins coûteuse est d'en faire moins.
- Mettre les références d'éléments en cache. Recherchez un élément une seule fois et stockez-le dans une variable plutôt que d'appeler
document.querySelectorà chaque itération. - Lire dans des variables locales. Les compteurs de boucle, les longueurs et les valeurs calculées doivent être stockés dans des variables JavaScript, pas relus depuis le DOM à chaque passage.
- Construire des chaînes, pas des nœuds, quand c'est approprié. Affecter
innerHTMLune seule fois est souvent plus rapide qu'insérer de nombreux nœuds un par un — bien que cela supprime les écouteurs d'événements et soit dangereux avec des données non fiables.
// Slow: re-queries and re-reads the DOM on every iteration
for (let i = 0; i < items.length; i++) {
document.getElementById('list').appendChild(makeRow(items[i]));
}
// Fast: resolve the reference once, outside the loop
const list = document.getElementById('list');
for (let i = 0; i < items.length; i++) {
list.appendChild(makeRow(items[i]));
}Consultez Selecting DOM Elements pour choisir des sélecteurs rapides et précis — préférez getElementById et querySelector ciblé plutôt que de longues chaînes de descendants comme div > ul li span.
Gestion efficace des événements
Attacher un écouteur à chaque élément d'une longue liste gaspille de la mémoire et ralentit les mises à jour du DOM. La délégation d'événements attache un seul écouteur à un parent commun et utilise event.target pour identifier quel enfant a été cliqué, en s'appuyant sur la propagation des événements.
// One listener handles the whole list, including rows added later
document.getElementById('list').addEventListener('click', (event) => {
const row = event.target.closest('li');
if (row) console.log('clicked row:', row.dataset.id);
});Cette approche passe à l'échelle pour des milliers d'éléments et couvre automatiquement les éléments ajoutés après la mise en place de l'écouteur. En savoir plus dans Event Handling in the DOM et Introduction to Browser Events.
Comprendre et éviter le layout thrashing
Qu'est-ce que le layout thrashing ?
Le layout thrashing survient lorsque vous entrelacez des lectures et des écritures de propriétés de mise en page en succession rapide. Chaque lecture force un layout synchrone pour vider l'écriture précédente, de sorte qu'une boucle lecture-écriture-lecture-écriture déclenche un reflow par itération au lieu d'un reflow au total.
// Bad: read (offsetWidth) forces layout, then write invalidates it — every loop
const boxes = document.querySelectorAll('.box');
boxes.forEach((box) => {
box.style.width = box.offsetWidth + 10 + 'px'; // read + write interleaved
});Corriger le problème : regrouper les lectures, puis les écritures
Regroupez toutes les lectures en premier, puis effectuez toutes les écritures. Le navigateur effectue un seul passage de layout pour les lectures et un seul pour les écritures.
const boxes = document.querySelectorAll('.box');
// 1. Read phase — collect every measurement first
const widths = [...boxes].map((box) => box.offsetWidth);
// 2. Write phase — now apply all changes; no read interrupts them
boxes.forEach((box, i) => {
box.style.width = widths[i] + 10 + 'px';
});Planifier le travail avec requestAnimationFrame
requestAnimationFrame exécute votre callback juste avant le prochain repaint, ce qui est le moment idéal pour écrire des modifications DOM — elles sont fusionnées en une seule image plutôt que de déclencher des repaints intermédiaires.
const element = document.getElementById('box');
requestAnimationFrame(() => {
const width = element.offsetWidth; // read
element.style.width = width + 10 + 'px'; // write, applied in the same frame
});Pour les animations, gardez toutes les écritures DOM à l'intérieur du callback requestAnimationFrame et ne lisez jamais la mise en page au milieu de celles-ci.
Regrouper les modifications du DOM
Utiliser document.createDocumentFragment()
document.createDocumentFragment() est un conteneur léger hors écran. Les nœuds ajoutés à un fragment ne font pas partie du document actif, donc sa construction ne déclenche aucun reflow. Lorsque vous ajoutez le fragment terminé à la page, le navigateur insère tous ses enfants en une seule opération — un reflow au lieu d'un par nœud.
Exemple
<!DOCTYPE html>
<html>
<head>
<title>Batching DOM Changes</title>
</head>
<body>
<div id="container"></div>
<script>
const container = document.getElementById('container');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 40; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment); // Batch update
</script>
</body>
</html>La boucle construit 40 éléments dans le fragment sans aucun impact sur la page visible. Seul le container.appendChild(fragment) final touche le DOM actif, de sorte que le navigateur effectue un seul passage de layout au lieu de 40. (Les moteurs modernes vous permettent également d'ajouter un tableau de nœuds en un seul appel avec container.append(...nodes), ce qui est également regroupé.)
Éviter le layout synchrone forcé
Une version subtile du thrashing consiste à lire une propriété de mise en page juste après une modification de style, ce qui force le navigateur à recalculer la mise en page immédiatement :
const box = document.getElementById('box');
box.classList.add('expanded'); // write — queues a layout change
const height = box.offsetHeight; // read — forces layout NOW to answerSi vous n'avez pas besoin de la valeur immédiatement, différez la lecture à la prochaine image avec requestAnimationFrame, ou restructurez le code pour que toutes les lectures se produisent avant toute écriture. Parmi les propriétés courantes qui déclenchent un layout forcé lors de leur lecture, on trouve offsetTop/offsetWidth/offsetHeight, clientWidth/clientHeight, scrollTop et getComputedStyle().
Bonnes pratiques
- Différer le JavaScript non critique. Ajoutez l'attribut
deferaux balises<script>pour que le navigateur continue à analyser le HTML et exécute le script une fois le DOM prêt. Utilisezasyncpour les scripts tiers indépendants. - Préférer les bascules de classes aux styles en ligne. Modifier une classe CSS permet au navigateur d'appliquer de nombreuses règles de style en un seul reflow, au lieu d'un reflow par affectation de
styleen ligne. - Animer
transformetopacity. Ces propriétés sont composées par le GPU et évitent entièrement la mise en page et le repaint, contrairement à l'animation dewidth,topoumargin. - Détacher, modifier, rattacher. Pour des modifications importantes, retirez un sous-arbre du document (ou masquez-le avec
display: none), modifiez-le hors écran, puis réinsérez-le. - Profiler avant d'optimiser. Utilisez le panneau Performance des DevTools du navigateur pour trouver le vrai goulot d'étranglement plutôt que de le deviner — voir DOM Debugging and Tools.
La règle d'or : regroupez vos lectures DOM ensemble, puis regroupez vos écritures ensemble. Chaque fois qu'une lecture suit une écriture, le navigateur est forcé de recalculer la mise en page de manière synchrone. Les regrouper lui permet d'effectuer le travail une seule fois par image.
Pièges courants
- Appeler
document.querySelectorà l'intérieur d'une boucle au lieu de mettre le résultat en cache. - Lire
offsetWidth/offsetHeightet écrire des styles dans la même itération de boucle. - Attacher un écouteur d'événements distinct à chaque élément d'une grande liste dynamique au lieu de déléguer.
- Animer des propriétés déclenchant une mise en page (
width,left,margin) au lieu detransform/opacity.
Conclusion
Les performances du DOM se résument à une idée : le navigateur est rapide pour regrouper son travail de rendu, et votre rôle est d'éviter de rompre ce regroupement. Mettez les références en cache, déléguez les événements, regroupez les lectures avant les écritures, construisez les grandes mises à jour dans un DocumentFragment, et planifiez les changements visuels avec requestAnimationFrame. En cas de doute, profilez — le panneau Performance des DevTools vous montrera exactement où se produisent les reflows. Approfondissez ensuite vos compétences avec DOM Manipulation et Advanced DOM Techniques.