Skip to main content

Chapitre 61 : Superposition et gestion des couches avec z-index et Grid

Concepts clés : Superposition d'items dans une même cellule de grille, interaction entre Grid et Stacking Context, optimisation du z-index.

En tant que développeur senior, vous savez que la superposition d'éléments est l'un des aspects les plus frustrants du CSS, principalement à cause de la complexité du Stacking Context (contexte d'empilement). Traditionnellement, pour superposer deux éléments, on utilisait position: absolute ou fixed, ce qui sortait l'élément du flux normal du document et compliquait la gestion du responsive.

L'avènement de CSS Grid a radicalement changé la donne. Grid permet désormais de superposer des éléments tout en les maintenant dans le flux du layout, offrant une alternative robuste et sémantique au positionnement absolu.

La Superposition Native via CSS Grid

1. Quoi

La superposition dans CSS Grid se produit lorsque deux ou plusieurs Grid Items sont assignés à la même zone de la grille (les mêmes lignes de début et de fin). Contrairement au flux standard où les éléments se poussent les uns les autres, Grid permet l'occupation simultanée d'un espace.

Lorsqu'une superposition a lieu, le navigateur détermine l'ordre d'affichage selon les règles du Stacking Context. Par défaut, l'élément déclaré en dernier dans le DOM apparaît au-dessus des autres.

2. Pourquoi

L'utilisation de Grid pour la superposition présente des avantages architecturaux majeurs par rapport à position: absolute :

  • Conservation du flux : L'élément superposé continue de contribuer à la taille de la cellule. Si un élément superposé grandit, la cellule s'adapte, évitant ainsi les débordements (overflow) imprévus.
  • Alignement simplifié : On peut utiliser place-items, justify-self ou align-self pour positionner précisément les éléments superposés à l'intérieur de la cellule sans calculer de top, left, right ou bottom.
  • Sémantique et Maintenance : Le code est plus lisible. On définit une structure de grille et on y place des éléments, plutôt que de créer des conteneurs position: relative artificiels pour ancrer des éléments absolus.

3. Comment

A. Syntaxe de base

Pour superposer deux éléments, il suffit de leur donner les mêmes coordonnées de grille.

.container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}

.item-1, .item-2 {
grid-column: 1;
grid-row: 1;
}

B. Cas concret : Carte produit avec badge et overlay

Imaginons un composant de carte produit où une image, un badge de promotion et un overlay d'information doivent cohabiter.

<div class="product-card">
<img src="product.jpg" alt="Produit" class="product-image" />
<span class="badge">Promo -20%</span>
<div class="overlay">
<button>Ajouter au panier</button>
</div>
</div>
.product-card {
display: grid;
/* On définit une seule cellule qui englobe tout */
grid-template-columns: 1fr;
grid-template-rows: 1fr;
width: 300px;
height: 400px;
position: relative;
}

/* Tous les enfants occupent la même cellule */
.product-image,
.badge,
.overlay {
grid-column: 1;
grid-row: 1;
}

.product-image {
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1; /* Couche de fond */
}

.badge {
justify-self: start;
align-self: start;
background: red;
color: white;
padding: 0.5rem;
z-index: 3; /* Toujours au-dessus */
}

.overlay {
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
color: white;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 2; /* Entre l'image et le badge */
}

.product-card:hover .overlay {
opacity: 1;
}

C. Limitations

Bien que puissante, cette technique a des limites :

  • Interactivité : L'élément situé au-dessus (z-index le plus élevé) capture tous les événements de souris. Pour permettre l'interaction avec les éléments en dessous, il faut utiliser pointer-events: none sur la couche supérieure.
  • Performance : Une superposition excessive d'éléments complexes peut augmenter le coût du "composite layer" pour le navigateur, impactant potentiellement le scroll sur mobile.

4. Zone de Danger

Erreur commune : Utiliser z-index sur un élément qui n'a pas de position définie ou n'est pas un enfant direct d'un contexte de stacking. Note : Dans Grid, les enfants directs sont des Grid Items. Le z-index fonctionne sur les Grid Items même si leur position est static.

Bonne pratique : Utiliser des variables CSS pour gérer les couches de z-index afin d'éviter la "course aux chiffres" (ex: z-index: 9999).

:root {
--z-index-bg: 1;
--z-index-content: 10;
--z-index-overlay: 20;
--z-index-modal: 30;
}

.product-image { z-index: var(--z-index-bg); }
.overlay { z-index: var(--z-index-overlay); }

Choix de la stratégie de superposition

Maîtrise du Stacking Context avec Grid

1. Quoi

Le Stacking Context est une hiérarchie tridimensionnelle créée par le navigateur pour déterminer quel élément s'affiche devant un autre. Un nouveau contexte de stacking est créé lorsqu'un élément possède :

  • Un z-index différent de auto ET une position autre que static.
  • Spécificité Grid : Un z-index différent de auto sur un Grid Item (même en position: static).
  • Une propriété opacity inférieure à 1.
  • Une propriété transform, filter, ou perspective différente de none.
  • Un will-change défini.

2. Pourquoi

Comprendre le Stacking Context est crucial car le z-index n'est pas global. Il est relatif au contexte parent. Si un élément est enfermé dans un contexte de stacking avec un z-index faible, aucun z-index interne (même z-index: 999999) ne pourra le faire passer devant un élément situé dans un contexte parent supérieur.

3. Comment

A. Le piège du parent "isolant"

Si vous créez un contexte de stacking sur le conteneur Grid, tous les enfants sont limités à ce contexte.

.grid-container {
display: grid;
position: relative;
z-index: 1; /* Crée un nouveau Stacking Context */
}

.item-1 {
z-index: 10; /* Relatif au container */
}

B. Stratégie de gestion des couches (Layering Strategy)

Pour des architectures complexes, adoptez une approche de "couches plates" :

  1. Base Layer : Éléments de structure (Grid).
  2. Interaction Layer : Boutons, liens, inputs.
  3. UI Layer : Tooltips, dropdowns.
  4. Global Layer : Modales, notifications.

En utilisant Grid, vous pouvez assigner chaque élément à sa couche sans casser le flux :

.layer-base { z-index: var(--z-index-bg); }
.layer-ui { z-index: var(--z-index-content); }
.layer-global { z-index: var(--z-index-modal); }

4. Zone de Danger

Erreur commune : Tenter de "forcer" un élément à passer devant un autre en augmentant indéfiniment le z-index sans vérifier si un parent crée un nouveau contexte de stacking.

Bonne pratique : Utiliser l'inspecteur du navigateur (onglet "Layers" dans Chrome DevTools) pour visualiser la pile de rendu et identifier quel élément crée le contexte isolant.

Questions clés

1. Pourquoi utiliser Grid pour superposer des éléments plutôt que position: absolute ?

Découvrir la réponse

Grid permet de superposer des éléments tout en les gardant dans le flux du document. Cela signifie que la taille de la cellule de grille s'adapte au contenu le plus grand, évitant ainsi les problèmes de responsive et les calculs manuels de dimensions.

2. Est-ce que z-index fonctionne sur un élément position: static dans une grille ?

Découvrir la réponse

Oui. C'est une exception majeure : les enfants directs d'un conteneur Grid (ou Flex) peuvent utiliser z-index même s'ils sont en position: static.

3. Que se passe-t-il si deux éléments dans la même cellule Grid ont le même z-index ?

Découvrir la réponse

Le navigateur utilise l'ordre d'apparition dans le DOM. L'élément déclaré en dernier sera affiché au-dessus de l'élément déclaré en premier.

4. Comment rendre un élément cliquable alors qu'un autre élément transparent est superposé dessus ?

Découvrir la réponse

Il faut appliquer pointer-events: none à l'élément supérieur. Cela permet aux événements de souris (clics, hovers) de "traverser" l'élément et d'atteindre celui qui se trouve en dessous.

5. Quel impact a opacity: 0.9 sur la superposition ?

Découvrir la réponse

L'application d'une opacité inférieure à 1 crée automatiquement un nouveau Stacking Context. Cela signifie que les enfants de cet élément ne pourront plus être comparés en termes de z-index avec des éléments extérieurs à ce parent.

Mise en pratique

Exercice 1 : Reproduction guidée (Niveau Facile)

Objectif : Créer un effet de "texte sur image" simple en utilisant Grid.

  • Créez un conteneur de 400px par 300px.
  • Placez une image et un titre <h1>.
  • Superposez-les exactement dans la même cellule.
  • Centrez le texte verticalement et horizontalement.
  • Assurez-vous que le texte est au-dessus de l'image.
  • Bonus : Utilisez des variables CSS pour gérer les valeurs de z-index (évitez les "magic numbers").
Découvrir la solution commentée
<div class="hero-grid">
<img src="https://via.placeholder.com/400x300" alt="Hero" class="hero-img" />
<h1 class="hero-title">Bienvenue</h1>
</div>
/* Système de couches centralisé : plus de "magic numbers" z-index */
:root {
--z-index-bg: 1;
--z-index-content: 2;
}

.hero-grid {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
width: 400px;
height: 300px;
}

.hero-img, .hero-title {
grid-column: 1;
grid-row: 1;
}

.hero-img {
width: 100%;
height: 100%;
object-fit: cover;
z-index: var(--z-index-bg);
}

.hero-title {
z-index: var(--z-index-content);
margin: 0;
color: white;
/* Centrage parfait grâce à Grid */
place-self: center;
text-align: center;
font-family: sans-serif;
}

Exercice 2 : Adaptation (Niveau Intermédiaire)

Objectif : Ajouter un système de badge dynamique.

  • Reprenez l'exercice 1.
  • Ajoutez un badge "Nouveau" en haut à droite de l'image.
  • Le badge doit être au-dessus du texte et de l'image.
  • Le badge doit rester visible même si on ajoute un overlay sombre au survol de la carte.
Découvrir la solution commentée
<div class="hero-grid">
<img src="https://via.placeholder.com/400x300" alt="Hero" class="hero-img" />
<div class="overlay"></div>
<h1 class="hero-title">Bienvenue</h1>
<span class="badge">Nouveau</span>
</div>
.hero-grid {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
width: 400px;
height: 300px;
cursor: pointer;
}

.hero-img, .overlay, .hero-title, .badge {
grid-column: 1;
grid-row: 1;
}

.hero-img {
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}

.overlay {
z-index: 2;
background: rgba(0,0,0,0);
transition: background 0.3s;
}

.hero-grid:hover .overlay {
background: rgba(0,0,0,0.4);
}

.hero-title {
z-index: 3;
place-self: center;
color: white;
margin: 0;
}

.badge {
z-index: 4; /* Plus élevé que l'overlay et le titre */
justify-self: end;
align-self: start;
background: gold;
padding: 4px 8px;
font-weight: bold;
font-size: 0.8rem;
}

Exercice 3 : Conception (Niveau Senior)

Objectif : Créer un composant de "Galerie Interactive" avec gestion de Stacking Context.

  • Créez une grille de 3x3 images.
  • Lorsqu'on clique sur une image, elle doit s'agrandir pour couvrir toute la grille (effet zoom).
  • L'image agrandie doit passer devant toutes les autres images de la grille.
  • Contrainte : Vous ne devez pas déplacer l'élément dans le DOM. Vous devez utiliser Grid et z-index.
  • Défi : Gérez le fait que les images sont dans des cellules différentes mais doivent se superposer lors du zoom.
Indice clé

Pour que l'expansion fonctionne, chaque item doit avoir un placement explicite dans la grille (via nth-child). Si les items sont placés automatiquement, le passage en .active déclenche une redistribution du layout. Utilisez grid-area: row / col pour ancrer chaque item dans sa cellule.

Découvrir la solution commentée
<div class="gallery">
<div class="gallery-item"><img src="https://picsum.photos/seed/1/150" alt="Image 1" /></div>
<div class="gallery-item"><img src="https://picsum.photos/seed/2/150" alt="Image 2" /></div>
<div class="gallery-item"><img src="https://picsum.photos/seed/3/150" alt="Image 3" /></div>
<div class="gallery-item"><img src="https://picsum.photos/seed/4/150" alt="Image 4" /></div>
<div class="gallery-item"><img src="https://picsum.photos/seed/5/150" alt="Image 5" /></div>
<div class="gallery-item"><img src="https://picsum.photos/seed/6/150" alt="Image 6" /></div>
<div class="gallery-item"><img src="https://picsum.photos/seed/7/150" alt="Image 7" /></div>
<div class="gallery-item"><img src="https://picsum.photos/seed/8/150" alt="Image 8" /></div>
<div class="gallery-item"><img src="https://picsum.photos/seed/9/150" alt="Image 9" /></div>
</div>
:root {
--z-index-item: 1;
--z-index-active: 10;
}

.gallery {
display: grid;
grid-template-columns: repeat(3, 150px);
grid-template-rows: repeat(3, 150px);
gap: 10px;
position: relative;
}

/* Placement explicite de chaque item dans sa cellule.
Sans cela, quand un item passe en .active (placement explicite),
les items auto-placés se redistribuent et cassent le layout. */
.gallery-item:nth-child(1) { grid-area: 1 / 1; }
.gallery-item:nth-child(2) { grid-area: 1 / 2; }
.gallery-item:nth-child(3) { grid-area: 1 / 3; }
.gallery-item:nth-child(4) { grid-area: 2 / 1; }
.gallery-item:nth-child(5) { grid-area: 2 / 2; }
.gallery-item:nth-child(6) { grid-area: 2 / 3; }
.gallery-item:nth-child(7) { grid-area: 3 / 1; }
.gallery-item:nth-child(8) { grid-area: 3 / 2; }
.gallery-item:nth-child(9) { grid-area: 3 / 3; }

.gallery-item {
overflow: hidden;
transition: all 0.3s ease;
cursor: zoom-in;
z-index: var(--z-index-item);
}

.gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}

/* État actif : l'item couvre toute la grille (lignes 1→3, colonnes 1→3).
Les autres items conservent leur position grâce au placement explicite. */
.gallery-item.active {
grid-column: 1 / -1;
grid-row: 1 / -1;
z-index: var(--z-index-active);
cursor: zoom-out;
}
document.querySelectorAll('.gallery-item').forEach(item => {
item.addEventListener('click', () => {
const isActive = item.classList.contains('active');
// Ferme tout item précédemment ouvert
document.querySelectorAll('.gallery-item.active').forEach(i => i.classList.remove('active'));
// Ouvre cet item s'il n'était pas déjà actif
if (!isActive) item.classList.add('active');
});
});