Gérer un rapport hauteur/largeur en CSS

Voir la vidéo

Lorsqu'on intègre des vidéos (via des iframe ou la balise video) on a souvent envie que l'élément respecte un ratio prédéfini quelle que soit la largeur de l'élément. Nous allons voir les différentes méthodes qu'il est possible d'utiliser pour résoudre cette problématique.

La propriété CSS aspect-ratio

La propriété aspect-ratio est encore à l'état de draft et est peu supportée mais elle offre une solution simple.

.video {
  aspect-ratio: 16/9;
  width: 100%;
  height: auto;
}

Ainsi, l'élément prendra automatiquement une hauteur en fonction du ratio défini. Cependant, il est important de noter que le ratio peut être outrepassé si on force une certaine hauteur.
Par exemple un max-height l'emportera sur le ratio :

.video {
  aspect-ratio: 16/9;
  width: 100%;
  height: auto;
  max-height: 350px;
}

Aussi, si le contenu dépasse en hauteur, la hauteur de l'élément sera adaptée pour éviter le débordement.

Le padding-bottom en %

En attendant le support de la propriété aspect-ratio on peut utiliser des méthodes alternatives pour obtenir un résultat similaire. Une propriété intéressante du padding est que si l'unité est en pourcentage le calcul se fait par rapport à la largeur du conteneur.

percentage : The size of the padding as a percentage, relative to the width of the containing block. Must be nonnegative.

On peut donc utiliser cette spécificité pour obtenir une hauteur basée sur la largeur.

.ratio {
    position: relative;
}
.ratio::before {
    content: '';
    display: block;
    /* ratio de 16/9 */
    padding-bottom: 56.25%;
}
.ratio > * {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
}

On peut aussi utiliser des variables CSS pour dynamiser les choses

.ratio::before {
  padding-bottom: calc(100% / (var(--ratio, calc(16/9))));
}

Cela permet de piloter le ratio via une simple propriété.

<div class="ratio">
  <iframe src="..." />
</div>
<div class="ratio" style="--ratio: 1 / 2">
  <iframe src="..." />
</div>

L'inconvénient de cette approche est qu'il n'est pas possible de pousser la hauteur de l'élément si le contenu déborde.

display: grid

Cette technique peut être améliorée en remplaçant le système de position absolue par une grille de 1 par 1.

.ratio {
    position: relative;
    display: grid;
}
.ratio::before {
    content:'';
    padding-bottom: calc(100% / (var(--ratio, calc(16/9))));
    grid-area: 1 / 1;
}
.ratio > * {
    width: 100%;
    height: 100%;
    grid-area: 1 / 1;
}
/* Pour éviter que les images / iframe poussent le contenu, on utilise la précédente approche */
.ratio > img,
.ratio > iframe {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
}

Grâce à cette approche, on obtient le comportement de débordement si l'enfant de l'élément dépasse.

SVG pour gérer le ratio

L'inconvénient du padding-bottom est qu'il n'est pas possible de limiter sa hauteur via un max-height ou un min-height. Il est possible de remplacer le pseudo élément par un SVG pour forcer le ratio.

<div class="ratio">
  <svg viewBox="0 0 16 9"></svg>
  <img src="..." />
</div>

<div class="ratio">
  <svg viewBox="0 0 16 9"></svg>
  <div>
    Bonjour le monde
  </div>
</div>

On donne au SVG une viewbox pour forcer un ratio spécifique (ici 16/9ème). On lui donnera ensuite une largeur de 100% en CSS et la hauteur sera calculée automatiquement par le navigateur.

.ratio {
    position: relative;
    display: grid;
}
/* Le SVG va imposer le ratio */
.ratio > svg {
    width: 100%;
    height: auto;
    /* On peut forcer une hauteur max ou min max-height: 200px; */
}
.ratio > * {
    grid-area: 1 / 1;
}
/* Pour éviter que les images / iframe pousse le contenu, on utilise l'approche absolue */
.ratio > img,
.ratio > iframe {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
}

Si vous ne souhaitez pas « polluer » votre structure HTML avec un élément vide, il est aussi possible d'utiliser le SVG directement via la propriété content.

.ratio {
    position: relative;
    display: grid;
}
.ratio::before {
    content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 9'%3E%3C/svg%3E");
    width: 100%;
    height: auto;
    grid-area: 1 / 1;
}
.ratio > * {
    grid-area: 1 / 1;
}
/* Pour éviter que les images / iframe pousse le contenu, on utilise l'approche absolue */
.ratio > img,
.ratio > iframe {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
}

Cette solution est à mon sens la plus complète

  • On peut forcer une hauteur via un min-height ou un max-height sur le SVG
  • Le contenu peut déborder et faire grandir la boîte.

En revanche, il n'est pas possible d'utiliser une variable CSS pour pouvoir piloter le ratio (dans ce cas là on peut revenir à la méthode du SVG dans le code HTML).

Publié
Technologies utilisées
Auteur :
Grafikart
Partager