Avant toute chose, un peu de révision en lisant Comprendre z-index en CSS sur MDN.
Une fois qu'on a à peu près compris le fonctionnement, ça va aider pour la suite.
Il est très (trop) fréquent de voir des sélecteurs CSS avec des propriétés, en dur, du style z-index: 99999 ou carrément z-index: 2000000000 comme vu récemment sur une modale gérée par une API externe.
C'est assez marrant, car chacun se dit que « son z-index » sera plus fort que celui du voisin. Tu parles Charles !
In fine, on se retrouve avec une soupe de z-index inutilisable, avec des surcharges en veux-tu en voilà, alors qu'un tout petit peu de logique suffit.
Idéalement, à la construction des templates de pages on peut déjà poser ses propres bases.
Par exemple, sur ces pages, la structure générale est la suivante :
navqui contient les liens d'évitement pour l'accessibilité.
z-index: 6regionqui contient le menu d'option d'affichage qui doit toujours être superposé au reste de la page.
z-index: 5pour les deux éléments.headerpotentiellement "sticky" avec une navigation responsive.
z-index: 4nav(fil d'Ariane si présent) n'a pas de z-index défini, donc il est implicitementauto.mainpuisfooterdoivent rester « en-dessous » des éléments précédents et avoir une base d'empilement inférieure pour d'éventuelles zones ayant elle-mêmes des définitions de z-index.
z-index: 3etz-index: 2
Et c'est tout.
Pour résumer, à partir du moment où des zones (landmarks) peuvent être positionnées en fixed, absolute ou sticky, elles doivent recevoir un niveau d'empilement décroissant en partant du haut de page.
La fonction
Le principe est simple : elle récupère les éléments dont le z-index est défini et supérieur à 0, et renvoie la valeur la plus haute + 1.
(() => {
'use strict';
/**
* Récupère le z-index maximum des éléments DOM et retourne max + 1
* @param {HTMLElement|string|null} [n] - Nœud DOM, sélecteur CSS, ou null/undefined pour body *
* @param {boolean} [direct=false] - Si true, cherche uniquement les enfants directs (> *)
* @returns {number} Z-index maximum + 1, ou 1 si aucun trouvé
*/
window.getMaxZIndex ??= (n, direct = false) => {
const s = direct ? ' > *' : ' *';
const e = !n ? document.querySelectorAll('body' + s)
: typeof n === 'string' ? document.querySelectorAll(n + s)
: n instanceof HTMLElement ? n.querySelectorAll(direct ? ':scope > *' : '*')
: document.querySelectorAll('body' + s);
/**
* @type {number[]}
*/
const z = [...e].map(el => Number(getComputedStyle(el).zIndex) || 0).filter(z => z > 0);
return z.length ? Math.max(...z) + 1 : 1;
};
/**
* Alias pour getMaxZIndex
* @param {HTMLElement|string|null} [n] - Nœud DOM, sélecteur CSS, ou null/undefined pour body *
* @param {boolean} [direct=false] - Si true, cherche uniquement les enfants directs
* @returns {number} Z-index maximum + 1
*/
window.zzTop = (n, direct) => window.getMaxZIndex(n, direct);
/**
* Au chargement du DOM, applique automatiquement un z-index aux éléments avec data-zztop
* - Si data-zztop est un nombre valide, applique cette valeur
* - Sinon (NaN, 0, vide), applique le z-index maximum + 1
*/
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-zztop]').forEach(el => {
const zz = +el.dataset.zztop;
el.style.zIndex = `${zz || window.zzTop()}`;
});
});
})();
Temps moyen d'exécution calculé sur une page "lourde" : 0 milliseconde.
Un paramètre optionnel direct permet de sélectionner la profondeur des éléments à trouver :
- si
true: récupère seulement les noeuds enfants du sélecteur (' > *') - si
false(par défaut) : récupère tous les éléments dans le noeuds (' *').
Maintenant, il ne reste plus qu'à utiliser cette fonction à l'ajout d'un élément superposé dans la page pour lui spécifier son niveau d'empilage avec :
document.querySelector('ELEMENT_TRUC').style.zIndex = window.getMaxZIndex?.();
Le bonus
La fonction getMaxZIndex() a un alias, juste pour rire : zzTop().
En supplément, sur le DOMContentLoaded, un petit appel permet de gérer des attributs data-zztop="[number]" pour appliquer ponctuellement des niveaux d'empilement. Par exemple, sur les modules de recherche, ça permet d'avoir à coup sûr les articles ou les photos en-dessous de la liste de résultats.
That's all Folks.