Vous venez de coder un composant TwitterButton
(avec React, Vue, en suivant une méthodo BEM, OOCSS, ou autre: c'est comme vous voulez) et franchement c'est du beau boulot: le rendu est vraiment très joli, kudos au designer.
Seulement très vite, ce dernier jette un coup d'oeil à la recette et vous fait un petit retour parce qu'il :
Vous ajoutez donc quelques media queries pour adapter le style de ce bouton en fonction de la largeur du viewport. Ses dimensions changent maintenant lorsque la page fait plus de 768px
de large, puis lorsqu'elle fait plus de 968px
et enfin plus de 1200px
. Un chouia fastidieux.
Vous pestez un peu sur votre collègue qui aurait dû vous fournir toutes les maquettes (alors qu'il n'a pas forcément eu le temps de les créer) et lui peste car vous l'avez dérangé toutes les 2 minutes pour obtenir ces mesures intermédiaires.
Il vous reste 72 composants à coder. Super ambiance dans les bureaux 👏🏼
Plutôt que de demander à votre supérieur Jean-Michel de prendre parti pour résoudre ce problème, nous allons faire appel aux MATHS.
Des termes foutrement complexes pour définir quelque chose de très simple: il s'agit de faire transiter une valeur γ de α à β de façon linéaire et dans notre cas borné dans un intervalle donnée.
En partant de ça, nous allons définir une UI fluide à l'aide de 3 variables :
baseFontSize: number (px value)
scaleRatio: number (abs value)
fluidRange: [number (px value), number (px value)]
Prenons l'exemple d'un site web où, en mobile-first, la taille de police par défaut (baseFontSize
) est de 16px
. On souhaiterait que celle-ci soit de 20px
lorsque le viewport fait plus de 1600px
de large (donc que le coefficient d'agrandissement - scaleRatio
- soit de 20 / 16 = 1.25
) et que la transition pour passer de 16 à 20 ne se déclenche pas avant que le viewport fasse au moins 480px
de large.
La fonction suivante va nous permettre d'obtenir cette fameuse interpolation linéaire sous le forme d'une formule CSS avec calc()
:
// on utilise JS par praticité
let getLinearInterpolation = (
baseFontSize, // number
scaleRatio, // number
fluidRange // [number, number]
) => {
let maxFontSize = baseFontSize * scaleRatio;
let [rangeStart, rangeEnd] = fluidRange;
let multiplier = (baseFontSize - maxFontSize) / (rangeStart - rangeEnd);
let fixed = maxFontSize - multiplier * rangeEnd;
return `calc(${fixed}px + ${100 * multiplier}vw)`;
};
Si vous copiez-collez ça comme un sagouin dans la console devtools de votre navigateur web et tentez un essai avec les valeurs de notre exemple, vous obtiendrez normalement :
Voyons maintenant comment nous servir de ça.
L'intérêt de cette valeur, c'est qu'elle va nous permettre de modifier toutes les dimensions que l'on veut de façon progressive et proportionnelle.
Petit exemple, simple, basique :
<html>
<body>
<h1 class="title">Hello world</h1>
<div class="red-block"></div>
</body>
</html>
// exemple avec SCSS
html {
font-size: 16px; // baseFontSize
}
@media (min-width: 480px /* fluidRange start */) {
html {
// l'interpolation linéaire
font-size: calc(14.285714285714285px + 0.35714285714285715vw);
}
}
@media (min-width: 1600px /* fluidRange end */) {
html {
font-size: 20px; // baseFontSize * scaleRatio
}
}
// par défaut dans les navigateurs 1rem = 16px, cette fonction nous simplifie les divisions
// si vous faites du CSS-in-JS, let fluid = v => `${v / 16}rem` fait le job
@function fluid($value) {
@return $value / 16 + rem;
}
.title {
// si largeur du viewport < 480px -> font-size = 24px
// si largeur du viewport > 1600px -> font-size = 24 * 1.25 = 30px
// si 480px < largeur du viewport < 1600px -> 24px < font-size < 30px
font-size: fluid(24);
}
.red-block {
background-color: red;
// les dimensions seront contenues entre 100px et 100 * 1.25 = 125px
height: fluid(100);
width: fluid(100);
}
(On va se mentir et tenter d'ignorer le fait que tout le monde utilise le zoom)
En effet, l'utilisateur peut toujours choisir d'avoir une taille de police plus petite ou plus grande que celle par défaut (16px
) et c'est franchement pas très accessible de forcer.
On va donc modifier notre fonction JS et tenir compte de ça.
let getCSSFluidConfig = (
baseFontSize, // number
scaleRatio, // number
fluidRange // [number, number]
) => {
let toRem = value => value / 16;
let maxFontSize = baseFontSize * scaleRatio;
let baseRemFontSize = toRem(baseFontSize);
let maxRemFontSize = toRem(maxFontSize);
let [rangeStart, rangeEnd] = fluidRange;
// on évite rem pour les media queries: merci Safari
// pas de soucis pour utiliser toRem malgré tout:
// les media queries sont à la racine du document
let emRangeStart = toRem(rangeStart);
let emRangeEnd = toRem(rangeEnd);
let multiplier =
(baseRemFontSize - maxRemFontSize) / (emRangeStart - emRangeEnd);
let fixed = maxRemFontSize - multiplier * emRangeEnd;
// on en profite également pour retourner l'intégralité du CSS voulu
return `html { font-size: ${baseRemFontSize}rem }
@media (min-width: ${emRangeStart}em) {
html { font-size: calc(${fixed}rem + ${100 * multiplier}vw) }
}
@media (min-width: ${emRangeEnd}em) {
html { font-size: ${maxRemFontSize}rem }
}`;
};
Et voilà ! Ça continue de faire ce que l'on veut, mais en prenant en compte la taille de police par défaut définie par l'utilisateur.
Vous vous êtes empressé d'embêter le designer à nouveau afin de déterminer ces 3 variables ensemble: ça sera donc une font-size
comprise entre 16px
et 18px
(donc une UI qui scale jusqu'à 18 / 16
= 1.125
…vous êtes encore frileux à l'idée) entre 480px
et 1440px
!
Il est maintenant temps de modifier ce fameux bouton.
getCSSFluidConfig(16, 1.125, [480, 1440]);
/* -> "html { font-size: 1rem }
@media (min-width: 30em) {
html { font-size: calc(0.9375rem + 0.20833333333333334vw) }
}
@media (min-width: 90em) {
html { font-size: 1.125rem }
}" */
// le code généré
html {
font-size: 1rem;
}
@media (min-width: 30em) {
html {
font-size: calc(0.9375rem + 0.20833333333333334vw);
}
}
@media (min-width: 90em) {
html {
font-size: 1.125rem;
}
}
// fonctions utilitaires
@function fluid($value) {
@return $value / 16 + rem;
}
// le reste du CSS
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
.twitter-btn {
align-items: center;
display: flex;
background-color: #1da1f3;
border: 1px solid #218de4; // il ne serait pas logique que border-width soit fluide
border-radius: 4px; // idem pour border-radius
padding: fluid(4);
padding-right: fluid(8);
box-shadow: 0 5px 5px -5px rgba(0, 0, 0, 0.25);
}
.twitter-btn svg {
height: fluid(24);
width: fluid(24);
}
.twitter-btn span {
color: #fff;
font-size: fluid(14);
margin-left: fluid(4);
}
Ainsi,
font-size
par défaut fera entre 1rem
et 1.125rem
16px
et 18px
si réglage navigateur par défaut)padding
de .twitter-btn
fera entre 0.25rem
et 0.28125rem
4px
et 4.5px
)height
et la width
du svg feront entre 1.5rem
et 1.6875rem
24px
et 27px
)La différence est très subtile (mais vous étiez frileux). L'avantage, c'est que si vous changez d'avis dans 3 semaines pour finalement passer sur un agrandissement de x1.5 à 2560px
de large, il vous suffira générer un nouveau ce petit bout de code, de copier/coller les quelques lignes obtenues au début de votre fichier CSS …et c'est tout ! Inutile de revenir dans les composants ou de refaire un quelconque calcul.
Pour que ce soit encore plus simple, je vous ai concocté un petit générateur :
Il ne vous qu'à profiter de toute ces heures gagnées en invitant votre (maintenant pote) graphiste à boire une bière ! 🍻