Rendre une police totalement responsive avec la fonction CSS clamp et un peu de Math.

Le comportement responsive d'une page Web se fait à l'aide des breakpoints CSS qui par définition propose d'appliquer des règles supplémentaires lorsque les dimensions de la page Web sont inférieures/comprises/superieures entre des valeurs fixées par le développeur. Le problème c'est que pour les tailles des polices/fonts de certains éléments spécifiques, cette vision en escalier est insuffisante.

Mise en situtation

Que ce soit à l'aide d'un framework CSS ou from scratch, une bonne pratique est de ne pas utiliser un trop grand nombre de breakpoints mais plutôt d'essayer de se calquer sur des familles de périphériques. Par exemple dans Bootstrap5, il y a 6 breakpoints de base :

Ce découpage permet de bien cibler les différents type de périphérique et ainsi de proposer des templates différents en jouant sur les dispositions, les tailles...

Si je prends l'exemple d'une topbar, avec un titre contenant le nom du site étalé sur toute la largeur (width:100%). Pour que ça rentre sur une ligne, on va par exemple définir une taille minimal du bloc et une taille de police (min-width:320px, font-size:1.5rem) comme règles de base.

Puis ensuite, on va de manière ascendante, c'est à dire du plus petit périphérique au plus grand écrire des règles qui surchargent les anciennes.

Pour notre topbar, cela veut dire qu'il faut définir un breakpoint (media-query ≥ 576) qui comporte une règle qui rédéfini la taille de la police en plus gros (font-size:2.5rem).

Sauf que l'on se rend bien compte que pour des largeurs autour des 500px, la taille de la police n'est pas adaptée, elle mériterait de prendre plus de largeur, surtout quand c'est le nom du site...

On peut multiplier les breakpoints mais cela rajoute du code et l'on aura toujours un comportement en escalier alors que l'on cherche finalement plutôt à avoir une taille qui s'adapte linéairement.

La fonction clamp()

Comme le dit la documentation, clamp() est une function CSS qui retourne une valeur de type "taille" à partir de trois paramètres.

Comme c'est une fonction, elle peut être utilisée par toute les propriétés qui utilise des tailles (width, height, font-size,..) et combinée avec d'autres fonctions (min, max, calc,...).

Le coté dynamique s'obtient en définissant la valeur préférée dans une unité dynamique, comme par exemple les % ou les vw.

Pour tout ce qui est taille des éléments comme les blocs, la fonction est assez simple à utiliser, par contre, dans le cas des polices cela devient beaucoup plus obscure car on utilise des unités complétements différentes (rem vs vw).

On sent bien que ce n'est pas évident d'imaginer une correlation entre la variation de la largeur de la page (qui évolue entre 320px et 1400px) et la taille de la police exprimée en rem et c'est encore plus flagrant avec des polices exotiques.

Un peu de théorie

Pour la théorie complète cela se passe dans cet article en anglais, mais cela peux se résumer à un seul graphique (extrait de l'article).

Evolution de la taille de la police en fonction de la taille de la page.

L'idée c'est d'essayer de calculer la pente de la droite et de l'exprimer par rapport à la largeur. Pour cela on a besoin de 4 paramètres à définir en fonction de sa police et de son template (360px, 1rem, 840px, 3.5rem).

Le calcul de la pente se fait via la formule ci dessous avec maxWidth et minWidth qui pour le calcul doivent être convertis en rem (division par 16 sur la base de 1rem = 16px).

Formule du calcul de la pente
slope = (maxFontSize - minFontSize) / (maxWidth - minWidth)
// soit avec nos valeurs
slope = (3.5 - 1) / ( (840 - 360) / 16)
slope = 2.5 / 30
slope = 0.083333333

Ensuite on a besoin de calculer le point d'intersection avec l'axe des Y défini par la formule suivante.

Formule du calcul du point d'intersection de l'axe des Y.
yAxisIntersection = -minWidth * slope + minFontSize
// soit avec nos valeurs
yAxisIntersection = -(360/16) * 0.083333333 + 1  
yAxisIntersection = -22.5 * 0.083333333 + 1
yAxisIntersection = -1.874999992 + 1
yAxisIntersection = -0.874999992

Enfin, l'expression de la valeur préférée peut-etre définie par la formule suivante.

Expression de la valeur préférée.
preferredValue = yAxisIntersection[rem] + (slope * 100)[vw]
// soit avec nos valeurs arrondies
preferredValue = -0.875rem + 8.333vw

Voilà, notre taille est défini avec une base fixe et un paramètre variant en vw. Pour rappel, le vw est une unité de type pourcentage sur la largeur de la page que l'on peut utiliser dans tous les contextes (contrairement au simple %).

Valeur CSS de la propriété font-size.
font-size:clamp(1rem, -0.875rem + 8.333vw, 3.5rem);

1rem = 16px ?

Pour avoir des données calculable, on part du principe que 1rem est égale à 16px, cette valeur est issue du règlage par défaut du navigateur mais peut être surchargée en définissant la propriété font-size du noeud body.

Le principe d'utilisation de cette unité est donc de définir les tailles des polices relativement à une référence fixe contrairement au em qui s'applique en cascade au blocs parents. C'est d'ailleurs la force principale de cette unité. Y'a un coté magique à voir grossir/rappetissir en propritions les polices en changeant uniquement la font-size du noeud body.

Sauf que si le design du site redéfini cette propriété, comme c'est le cas de ce billet (font-size à 1.15rem soit 18px), le calcul est un peu différent.

Un peu de code

Pour terminer, rien de mieux qu'un peu de code pour calculer automatiquement la propriété clamp accompagné d'une simulation pour se rendre compte du résultat.

Largeur (px)
min
max
Police (rem)
min
max
Rem (px)

Largeur (px) 360 600 840
Police (rem) 1 2.25 3.5

Retour aux billets Billet plus ancien : Déploiement d'une application PHP sur un serveur mutualisé OVH qui bloque le clonage d'un dépôt depuis une instance gitlab privée.