Qu’est-ce que la fusion des marges?

La fusion des marges est un mécanisme décrit dans la spécification CSS (CSS 2.1: Collapsing Margins). Il concerne les marges verticales (margin-top et margin-bottom) des éléments de type bloc.

C’est un mécanisme qui, pour deux blocs qui se suivent, «fusionne» la marge inférieure du premier et la marge supérieure du deuxième. C’est-à-dire qu’au lieu d’additionner les deux marges, le navigateur va conserver la plus grande des deux. Ainsi, dans l’exemple suivant l’écart entre les deux paragraphes ne sera pas de 50 pixels (20 + 30), mais de 30 pixels:

<p style="margin-bottom: 20px;">
    Premier paragraphe
</p>
<p style="margin-top: 30px;">
    Deuxième paragraphe
</p>

Pourquoi ce mécanisme?

La fusion des marges est très utile pour obtenir des espaces harmonieux entre les portions de texte. Grâce à la fusion des marges, on peut considérer que pour chaque paragraphe, titre, liste ou autre élément contenant une portion de texte:

  1. la marge supérieure correspond au retrait minimal souhaité avant l’élément;
  2. la marge inférieure correspond au retrait minimal souhaité après l’élément.

Voici par exemple un code CSS simple qui exploite ce mécanisme, pour obtenir des espaces harmonieux entre les paragraphes:

h1, h2, h3, h4, h5, h6 {
    margin-top: 1.5em;  /* soit 150% de la taille du texte */
    margin-bottom: .5em;  /* ...50% de la taille du texte */
}
ul, ol, p {
    margin-top: .75em;
    margin-bottom: .75em;
}

Avec ce code, on utilise un retrait identique avant et après les contenus «simples» (paragraphes, listes), pour qu’ils s’enchainent de manière régulière. Par contre, on souhaite que les titres soient éloignés des contenus qui les précèdent, et pas trop éloignés des contenus qui les suivent, vu que chaque titre décrit les contenus qui le suivent.

Fusion des marges entre un élément et son parent

La fusion des marges ne se limite pas aux éléments frères (par exemple deux paragraphes qui se suivent), mais s’applique aussi aux éléments parents et enfants (par exemple un paragraphe dans un conteneur div).

Exemple simple de fusion des marges enfant—parent

Si nous plaçons un paragraphe avec des marges inférieures et supérieures dans un élément div:

div {
    margin: 10px;
    background-color: #8CF; /* Bleu clair */
}
p {
    margin: 50px;
    background-color: #909; /* Violet */
}
<div>
    <p>Un paragraphe</p>
</div>

Nous obtenons le résultat suivant:

Un paragraphe

Que constate-t-on? Les marges verticales du paragraphe, au lieu de créer un espace vide entre les bords du div et ceux du paragraphe, fusionnent avec les marges verticales du div. Le retrait au-dessus du div sera donc de 50 pixels, au lieu de 10 pixels. Ce comportement est normal, même s’il peut être surprenant.

Exemple avec un texte qui fait obstacle à la fusion des marges

Notons que la fusion des marges ne se produit que pour la marge supérieure du premier enfant et la marge inférieure du dernier enfant d’un bloc. De plus, si le bloc contient du texte au début ou à la fin, la fusion des marges ne se produit pas. C’est ce que démontre l’exemple suivant.

<div>
    Parent
    <p>Un paragraphe</p>
    <p>Un deuxième paragraphe</p>
</div>

Les marges sont de 10px et 50px comme précédemment. Ce qui nous donne:

Parent

Un paragraphe

Un deuxième paragraphe

Seule la marge inférieure du dernier paragraphe fusionne avec la marge du conteneur (élément div). Les marges entre les deux paragraphes fusionnent entre elles pour donner un retrait de 50 pixels.

Fusion des marges sur plusieurs niveaux

Si rien ne l’empêche, le mécanisme de fusion des marges peut jouer sur plusieurs niveaux:

Un paragraphe

Parent du paragraphe
Grand-père du paragraphe
Arrière-grand-père du paragraphe

Dans cet exemple, la marge supérieure du paragraphe (50px) fusionne avec la marge supérieure du bloc vert (10px), qui fusionne avec la marge supérieure du bloc rose (10px), qui à son tour fusionne avec la marge supérieure du bloc bleu (10px). Au final, on n’a qu’une marge unique de 50 pixels!

Empêcher la fusion des marges entre parent et enfant

Dans certains cas, la fusion des marges n’est pas souhaitable. C’est surtout le cas pour les conteneurs et leurs éléments enfants: une marge qui s’applique au mauvais niveau peut «casser» l’affichage d’une page web.

Dans tous les exemples suivants, le CSS de base est:

div {
    margin: 10px;
    background-color: #8CF; /* Bleu clair */
}
p {
    margin: 50px;
    background-color: #909; /* Violet */
}

1. Avec du padding

La technique la plus simple pour éviter la fusion des marges entre un élément et son parent est d’utiliser du padding sur l’élément parent. Reprenons notre premier exemple de fusion entre parent et enfant:

<div style="padding: 1px 0;">
    <p>Un paragraphe</p>
</div>

On a juste rajouté un pixel de padding en haut et en bas de notre div conteneur. Et là, magie, plus de fusion des marges:

Un paragraphe

Utilisez du padding (et notamment la déclaration padding: 1px 0;) dès que vous avez besoin d’empêcher une fusion de marges. Vous pouvez aussi l’utliser de manière préventive pour vos principaux conteneurs. Ce n’est pas une solution miracle, mais le padding fera l’affaire dans 95% des cas.

2. Avec des bordures (propriété border)

Les bordures ont le même effet que le padding:

<div style="border: 1px solid;">
    <p>Un paragraphe</p>
</div>

Un paragraphe

3. Avec un contexte de formatage (propriété overflow)

La notion de contexte de formatage est définie dans l'article "Le contexte de formatage block en CSS".

L’utilisation d’un contexte de formatage, via la propriété overflow et les valeurs auto ou hidden, empêche la fusion des marges.

<div style="overflow: hidden;">
    <p>Un paragraphe</p>
</div>

Un paragraphe

Cette solution est intéressante, mais a d’autres conséquences (empêche le dépassement des flottants, peut cacher les contenus qui dépassent ou faire apparaitre des barres de défilement). À utiliser avec prudence.

Note: si vous utilisez déjà un overflow: hidden; pour empêcher le dépassement des flottants, cela aura pour effet «secondaire» d’empêcher la fusion des marges également.

4. Avec les principales propriétés de positionnement

Les marges des éléments flottants, positionnés en absolu ou en fixe, ne fusionnement pas. Dans la pratique, on ne va pas utiliser le positionnement absolu ou flottant pour empêcher la fusion des marges, mais il peut être utile de savoir que, lorsque vous utilisez ces types de positionnement, la fusion des marges entre le bloc positionné et ses enfants ne se produira pas.

Dans certains cas, cette absence de fusion des marges peut être gênante. Par exemple, si on crée deux colonnes avec deux éléments div, où seul le premier est flottant:

Colonne de gauche (flottante). Un premier paragraphe.

Et un deuxième paragraphe.

Colonne de droite. Un premier paragraphe.

Et un deuxième paragraphe.

Un paragraphe simple placé juste avant les deux colonnes suivantes.

Colonne de gauche (flottante). Un premier paragraphe.

Et un deuxième paragraphe.

Colonne de droite. Un premier paragraphe.

Et un deuxième paragraphe.

On voit que dans les deux cas (colonnes en haut et en bas), les marges des paragraphes sont contenues par le bloc flottant (en rose), mais pas par le deuxième bloc (en bleu, non flottant). Dans le premier cas, les blocs ne sont pas à la même hauteur, mais les textes sont alignés; dans le deuxième cas, les blocs sont au même niveau mais les textes ne sont plus alignés.

Pour obtenir un rendu plus égal, il faudra donc: soit faire flotter les deux blocs, soit empêcher la fusion des marges dans les deux cas, par exemple avec un padding: 1px 0;.

Note: le rendu de cet exemple n’est pas correct dans Internet Explorer 6 et 7. C’est dû à un bug polymorphe d’Internet Explorer (jusqu’à sa version 7), qui tend à faire disparaitre purement et simplement certaines marges. Pour voir le rendu correct, utilisez Firefox, Safari, Chrome, Opera, ou encore Internet Explorer 8+.

Fusion des marges, Internet Explorer et HasLayout

Le HasLayout est un mécanisme propre au moteur de rendu de Internet Explorer jusqu’à sa version 7. Il est décrit dans l’article suivant: HasLayout et bugs de rendu dans Internet Explorer 6-7. Dans Internet Explorer 6 et 7 notamment, conférer le layout à un élément empêche la fusion des marges entre parent et enfant. Par contre, cela n’empêche pas la fusion des marges entre éléments frères.

Dans l’exemple suivant, l’élément div et les deux paragraphes ont tous le layout, via la déclaration zoom: 1. Les paragraphes ont des marges de 50px.

Un paragraphe

Un deuxième paragraphe

Dans Internet Explorer 6 ou 7, on constate que la fusion des marges a bien lieu entre les deux paragraphes (on a bien un écart de 50 pixels), mais pas entre les paragraphes et leur conteneur.

Note: le mécanisme de HasLayout n’existe plus dans Internet Explorer 8+. Ce problème ne se présente donc plus.