Diagnostic initial : les cinq causes majeures de ralentissement
Pour optimiser les performances, nous avons utilisé les outils de diagnostic de Chrome et une revue de code approfondie. Cinq problèmes critiques ont été identifiés, courants dans la plupart des systèmes d'administration :
| Type de problème | Description | Impact sur les performances |
|---|---|---|
| Conflits dans les animations | Utilisation mixte de animate() en JS et de transition en CSS |
Blocage de la file de rendu du navigateur, animations saccadées avec un délai ≥200ms |
| Sollicitation excessive du DOM | Appels fréquents à .find(), .parent(), .children(), avec ≥15 requêtes DOM par interaction |
Déclenchement forcé de recalculs de mise en page et de repeintures, ralentissement perceptible |
| Calculs de mise en page dynamiques | Utilisation massive de l'unité % pour les largeurs, retraits et marges intérieures | Recalcul nécessaire à chaque réduction, provoquant des tremblements visuels |
| Perturbation des priorités de style | Abus de la déclaration !important pour forcer l'application des styles |
Temps de calcul des styles multiplié par 3 |
| Logique synchrone bloquante | Vérification synchrone de la validité des URL lors du rendu du fil d'Ariane | Blocage du thread principal, délai de réponse aux interactions ≥300ms |
Conclusion essentielle : les ralentissements proviennent d'animations incohérentes, d'opérations DOM non contrôlées, de calculs de style redondants et d'une exécution bloquante de la logique.
Restructuration CSS : réduire les recalculs et repeintures à la source
Le CSS constitue la première ligne de défense pour l'optimisation. Nous avons adopté une refonte complète plutôt que des ajustements mineurs.
1. Élimination des animations JS au profit des transitions CSS
Les animations JavaScript s'exécutent sur le thread principle et sont sujettes aux blocages. Les transitions CSS sont gérées par le thread de composition du navigateur, offrant une priorité et une stabilité supérieures.
Code avant optimisation (problématique) :
// Conflit entre animation JS et modification de styles CSS
$('.sidebar').animate({ width: '50px' }, 300);
$('.main-content').animate({ width: 'calc(100% - 50px)' }, 300);
Code après optimisation (solution) :
/* Utilisation unifiée des transitions, écoute des changements de classe */
.sidebar-menu {
width: 250px;
height: 100vh;
flex-shrink: 0;
transition: width 0.3s ease-out;
}
.sidebar-menu.collapsed {
width: 50px;
}
.main-content {
flex: 1;
transition: all 0.3s ease-out;
}
Avantages :
- Fréquence d'images passée de 30fps à 60fps sans saccades.
- Réduction de 50% du volume de code.
- Exécution sur le thread de composition, sans blocage du JavaScript.
2. Mise en page à dimensions fixes pour éviter les recalculs continus
Les mises en page en pourcentage nécessitent des recalculs constants lors des changements d'état.
Code avant optimisation :
.menu-item {
text-indent: 15%;
height: 10%;
}
Code après optimisation :
.menu-item,
.menu-parent {
width: 100%;
height: 50px;
text-indent: 38px;
line-height: 50px;
}
.sub-menu > .menu-item {
text-indent: 56px;
}
Bénéfice : le temps de calcul de mise en page est réduit de 70%, éliminant les tremblements lors de la réduction.
3. Ouverture des sous-menus avec max-height au lieu de slideToggle
La méthode slideToggle() de jQuery déclenche des recalculs fréquents. Nous utilisons max-height pour une animation fluide avec seulement deux recalculs (ouverture et fermeture).
Code avant optimisation :
$('.sub-menu').stop(true, true).slideToggle(500); // Recalculs haute fréquence
Code après optimisation :
.sub-menu {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
}
.sub-menu.open {
max-height: 2000px; // Valeur suffisante pour tous les niveaux
}
Logique : la simulation de changement de hauteur via max-height préserve l'animation fluide tout en réduisant considérablement les recalculs.
4. Nettoyage des !important et restructuration des priorités de style
L'abus de !important oblige le navigateur à recalculer constamment les priorités, impactant les performances.
Actions :
- Suppression de tous les
!important, contrôle via la spécificité des sélecteurs (ex:.sidebar-menu.collapsed #path). - Adoption de la convention de nommage BEM pour éviter les conflits.
- Extraction des styles communs (courbes d'animation, valeurs de couleur).
Résultat : temps de calcul des styles réduit de 60%, efficacité de rendu améliorée.
Optimisation JavaScript : mise en cache, asynchronisme et simplification
L'optimisation JS vise à réduire les calculs inutiles et les opérations DOM.
1. Mise en cache globale des nœuds DOM
Les requêtes DOM sont un goulot d'étranglement. Nous les exécutons une seule fois et réutilisons les références.
Code avant optimisation :
// Requête DOM à chaque opération, temps ≥10ms/fois
$('.sidebar').addClass('collapsed');
$('.sidebar').removeClass('collapsed');
Code après optimisation :
// Cache des nœuds DOM au chargement de la page
const sidebar = document.querySelector('.sidebar');
const mainContent = document.querySelector('.main-content');
const menuTooltip = document.getElementById('menuTooltip');
// Utilisation ultérieure du cache, temps ≤1ms/fois
sidebar.classList.toggle('collapsed', isSidebarCollapsed);
Bénéfice : temps de requête DOM réduit de 90%, vitesse de réponse améliorée de plus de 50%.
2. Pilotage par état : modification exclusive des classes
La logique JS gère l'état, les changements de style sont déclenchés par le basculement de classes.
// Logique de réduction/expansion (gestion d'état + basculement de classe)
let sidebarCollapsed = false;
document.querySelector('.toggle-btn').addEventListener('click', function() {
sidebarCollapsed = !sidebarCollapsed;
sidebar.classList.toggle('collapsed', sidebarCollapsed);
// Logique auxiliaire : changement d'icône basé sur l'état
logoImg.src = sidebarCollapsed ? '../img/log.png' : '../img/logo.png';
toggleBtn.style.transform = sidebarCollapsed ? 'rotate(180deg)' : 'rotate(0)';
});
3. Solution asynchrone avec cache pour la vérification des URL
La vérification synchrone des URL lors des navigations dans le fil d'Ariane bloquait le thread principal.
Solution :
- Envoi asynchrone de requêtes HEAD pour éviter le blocage.
- Utilisation d'un
Mappour mettre en cache les résultats.
// Cache pour les résultats de vérification
const urlCache = new Map();
function loadIframeContent(url) {
if (urlCache.has(url)) {
updateIframeDisplay(urlCache.get(url), url);
return;
}
const xhr = new XMLHttpRequest();
xhr.open('HEAD', url, true);
xhr.onload = function() {
const isValid = xhr.status !== 404;
urlCache.set(url, isValid);
updateIframeDisplay(isValid, url);
};
xhr.send();
}
function updateIframeDisplay(isValid, url) {
if (isValid) {
document.getElementById('page').src = url;
document.getElementById('page').style.display = 'block';
document.getElementById('alert').style.display = 'none';
} else {
document.getElementById('page').style.display = 'none';
document.getElementById('alert').style.display = 'block';
}
}
Impact : délai de réponse réduit de 300ms à moins de 50ms, sans blocage perceptible.
4. Simplification de la logique de surbrillance
La surbrillance initiale nécessitait des boucles multiples et des opérations DOM redondantes.
// Surbrillance du menu et de ses parents (une seule récursion + une opération DOM)
function highlightMenu(menuId) {
// Réinitialisation de toutes les surbrillances (une traversée DOM)
document.querySelectorAll('.menu-item, .menu-parent').forEach(el => {
el.classList.remove('highlight');
el.querySelector('.icon').src = el.querySelector('.icon').dataset.defaultSrc;
});
// Obtention récursive des IDs parents (calcul sans opération DOM)
const parentIds = getParentIds(menuId);
// Application de la surbrillance (opération DOM groupée)
const currentEl = document.querySelector(`[data-id="${menuId}"]`);
if (currentEl) {
currentEl.classList.add('highlight');
currentEl.querySelector('.icon').src = currentEl.querySelector('.icon').dataset.highlightSrc;
}
parentIds.forEach(id => {
const parentEl = document.querySelector(`[data-id="${id}"]`);
if (parentEl) {
parentEl.classList.add('highlight');
parentEl.querySelector('.icon').src = parentEl.querySelector('.icon').dataset.highlightSrc;
}
});
}
Optimisations : récursion limitée au calcul des données, opérations DOM groupées, synchronisation des icônes sans traversée supplémentaire.
Système d'animation unifié : garantie de fluidité
Toutes les interactions suivent trois règles fondamentales :
- Animation unifiée : tous les effets (réduction, expansion, survol) utilisent
transitionCSS avec une courbeease-out. - Pilotage par état : le JS ne gère que l'état (ex:
sidebarCollapsed), les changements de style sont déclenchés exclusivement par les classes. - Recalculs minimaux : modification limitée aux propriétés nécessaires (
width,max-height) pour éviter les recalculs complets.
Résultats :
- Réduction/expansion de la barre latérale : 0 ralentissement, délai nul, transition de 300ms.
- Ouverture des sous-menus : fluide, sanns clignotement.
- Surbrillance des menus : réponse immédiate.
- Expérience globale : fluide, atteignant le standard professionnel.
Quantification des résultats d'optimisation
| Dimension | Avant optimisation | Après optimisation | Amélioration |
|---|---|---|---|
| Délai de réponse à la réduction | ≥200ms | ≤50ms | 75%+ |
| Nombre de recalculs/repeintures | ≥15 par interaction | ≤3 par interaction | 80%+ |
| Temps d'exécution JS | ≥100ms/fois | ≤20ms/fois | 80%+ |
| Maintenabilité du code | Logique désordonnée, fort couplage | Modulaire, faible couplage | 90%+ |
| Évaluation utilisateur (1-10) | 6 (ralentissements évidents) | 9.5+ (fluide et imperceptible) | 58%+ |