Ratio

Problématique

On dispose d’un élément où l’on souhaite maîtriser la hauteur pour respecter un ratio précis.

Solutions

Solution 1

On fixe la largeur et la hauteur dans une unité absolue (px, …). Au besoin on adapte les dimensions aux différents cas d’utilisation.

.element { 
	width: 300px;
	height: 300px;
}

On voit bien ici la limite de cette façon de faire: pas de fluidité. Si la largeur de l’élément doit être variable, on est coincé.

Solution 2

On fixe la hauteur relativement à la largeur du viewport, en vw. Cela fonctionne relativement bien mais:

  • ce n’est utilisable que si la largeur de l’élément est directement liée à celle du viewport
  • cela réclame de déterminer quelle proportion de la largeur du viewport est occupé par l’élément
  • la largeur du viewport ne correspond pas à la largeur du document, autrement dit : 100% (pour un élément en pleine largeur) ne vaut pas forcément 100vw, typiquement quand il y a la barre de défilement. Et la largeur de cette barre varie d’un navigateur à l’autre. Ainsi sous Chrome, dans une résolution de 1920px, 100vw vaut 1920px et 100% vaut 1903px. On a donc un léger décalage.

Solution 3

Le padding trick ! Cela consiste à utiliser le pseudo-élément :before avec un padding-top en pourcentage représentant le ratio. Et là, peu importe la largeur de l’élément, qu’elle soit fixée ou non, le ratio sera toujours respecté. Cela parait curieux et contre-intuitif. Mais ça s’explique ! Des marges intérieures, comme elles influent sur les dimensions de l’élément, ne peuvent être calculées par rapport à l’élément lui-même lorsqu’elles sont exprimées en pourcentage. Elles sont donc calculées par rapport à son conteneur, et plus précisément par rapport à la largeur du conteneur. Pourquoi ? De façon générale, la hauteur d’une page dépend de son contenu là où l’on dispose d’une donnée de référence connue pour la largeur, celle du viewport. Des calculs de pourcentages basées sur la hauteur pourraient mener à des résultats inconsistants. Pour éviter cela, les calculs sont basées sur la largeur en toutes circonstances. Et c’est là que réside le cœur du trick : un padding-top en pourcentage étant calculé en fonction de la largeur du conteneur (même si celui-ci à une hauteur fixe), on peut donc introduire une notion de ratio. En utilisant un pseudo-élément, on considère que le conteneur est notre élément sur lequel va se baser le calcul.

.element { 
	width: 300px;
}
.element:before{
	content: '';
	display: block;
	padding-top: 100%;
}

Le problème à ce stade, c’est que le padding va pousser le contenu de l’élément et déformer celui-ci. Le pseudo-élément doit être là sans être là. Et s’agissant d’un bloc, indiquer une largeur et une hauteur nulles ne règle pas le problème. Il y a au moins trois solutions:

  • display: flex sur l’élément, le pseudo-élément se retrouve alors aligné horizontalement avec le contenu
  • float: left sur le pseudo-élément, même chose que précédemment sauf qu’il faut ajouter display: table et clear: both sur le pseudo-élément :after, c’est donc moins élégant mais ça marche partout
  • position: absolute sur le contenu (top, bottom, left et right à 0)

En fait, les deux premières solutions ont un inconvénient majeur, si le contenu est plus haut que l’élément, celui-ci se déforme pour s’adapter au contenu et on perd le ratio. La solution idéale est donc d’utiliser la troisième. C’est d’ailleurs celle mise en œuvre par Bootstrap pour l’affichage de vidéo avec ratio. Si le contenu est mixte on peut envisager d’ajouter un conteneur intermédiaire entre l’élément et le contenu (c’est alors ce conteneur qui sera en absolu).

See the Pen ratio by web-cooking-factory (@web-cooking-factory) on CodePen.dark