Et si nous pouvions implémenter les tailles de contribution des concepteurs de manière transparente ? Et si nous pouvions définir des points d’ancrage personnalisés pour générer une valeur de réponse parfaite, nous donnant plus d’options à mesure que les approches de dimensionnement fluide ? Et si nous avions une formule magique qui contrôlait et synchronisait tout le projet ?
Il arrive souvent que je reçoive deux templates du designer : un pour mobile et un pour desktop. Dernièrement, je me suis demandé comment je pourrais automatiser le processus et optimiser le résultat. Comment implémenter les tailles spécifiées le plus efficacement ? Comment assurer une vue confortable de la tablette ? Comment puis-je optimiser la sortie pour les grands écrans ? Comment puis-je réagir à des rapports hauteur/largeur extrêmes ?
J’aimerais pouvoir lire les deux valeurs des différentes tailles (police, espaces, etc.) en pixels et les alimenter en arguments dans une fonction qui fait tout le travail pour moi. Je veux créer ma propre formule magique responsive, ma FabUnit.
Lorsque j’ai commencé à travailler sur ce sujet au printemps et que j’ai lancé FabUnit, je suis tombé sur cet article intéressant d’Adrian. Pendant ce temps, Ruslan et Brecht ont également fait des recherches dans cette direction et ont proposé des idées intéressantes.
Comment puis-je mettre en œuvre des modèles de conception plus efficacement ?
J’en ai assez d’écrire des requêtes multimédias pour chaque valeur et je veux éviter les ruptures de conception. L’entretien et le résultat ne sont pas satisfaisants. Alors, quelle est la meilleure façon de mettre en œuvre la contribution du concepteur ?

Qu’en est-il des tailles de fluides ? Il existe des calculatrices pratiques comme Utopia ou Min-Max-Calculator.

Mais pour mes projets, j’ai généralement besoin de plus d’options de configuration. La vue de la tablette est souvent trop petite et je ne peux pas réagir aux fenêtres plus grandes ou au rapport d’aspect.

Et ce serait bien d’avoir une synchronisation proportionnelle sur l’ensemble du projet. Je peux définir des variables globales avec les valeurs calculées, mais je veux aussi pouvoir générer une valeur interpolée localement dans les composants sans aucun effort. Je voudrais tracer ma propre ligne de réponse. J’ai donc besoin d’un outil qui crachera la valeur parfaite en fonction de divers points d’ancrage (définitions d’écran) et automatisera les processus pour la plupart de mes projets. Mon outil doit être rapide et facile à utiliser, et le code doit être lisible et maintenable.

Sur quelles constantes des spécifications de conception nos calculs doivent-ils se baser ?
Reprenons notre exemple précédent :

La taille de la police du corps doit être 16px
sur mobile et 22px
sur le bureau (plus sur le guide de style complet plus tard). Les tailles de mobile doivent commencer à 375px
et s’adapter en permanence à 1024px
. Jusqu’à 1440px
, les tailles doivent rester statiquement au niveau optimal. Après cela, les valeurs doivent évoluer de manière linéaire jusqu’à 2000px
après quoi le max-wrapper prend effet.


Cela nous donne les constantes suivantes qui s’appliquent à l’ensemble du projet :
Xf 375px global screen-min
Xa 1024px global screen-opt-start
Xb 1440px global screen-opt-end
Xu 2000px global screen-max
La taille de la police du corps doit être d’au moins 16px
idéalement 22px
. La taille de police maximale dans 2000px
doit être calculé automatiquement :
Yf 16px local size-min
Ya 22px
Yb 22px local size-opt
Yu auto
Donc, à la fin de la journée, ma fonction devrait pouvoir prendre deux arguments, dans ce cas, 16
Oui 22
.
fab-unit(16, 22);
Le calcul
Si vous n’êtes pas intéressé par la dérivation mathématique de la formule, n’hésitez pas à passer directement à la section « Comment utiliser FabUnit ? ».
Alors commençons !
Définir les brides parentes
Tout d’abord, nous devons déterminer quelles pinces parents nous voulons configurer.
clamp1: clamp(Yf, slope1, Ya)
clamp2: clamp(Yb, slope2, Yu)

Pinces de combinaison et d’emboîtement
Maintenant, nous devons combiner les deux pinces. Cela pourrait être un peu délicat. Nous devons considérer que les deux lignes, slope1
Oui slope2
, ils peuvent se remplacer, selon leur degré d’asymétrie. Puisque nous savons que slope2
doit être de 45 degrés ou 100 % (m = 1), nous pouvons nous demander si slope1
est supérieur à 1. De cette façon, nous pouvons définir une pince différente en fonction de la façon dont les lignes se croisent.

Oui slope1
est plus raide que slope2
nous combinons les pinces comme ceci:
clamp(Yf, slope1, clamp(Yb, slope2, Yu))
Oui slope1
est plus plat que slope2
on fait ce calcul :
clamp(clamp(Yf, slope1, Ya), slope2, Yu)
Régler:
steep-slope
? clamp(Yf, slope1, clamp(Yb, slope2, Yu))
: clamp(clamp(Yf, slope1, Ya), slope2, Yu)
Définir le conteneur maximum en option
Que se passe-t-il si nous n’avons pas de conteneur max qui fige la mise en page au-delà d’une certaine largeur ?

Tout d’abord, nous devons déterminer quelles pinces parents nous voulons configurer.
clamp1: clamp(Yf, slope1, Ya)
max: max(Yb, slope2)
Oui slope1
est plus raide que slope2
:
clamp(Yf, slope1, max(Yb, slope2))
Oui slope1
est plus plat que slope2
:
max(clamp(Yf, slope1, Ya), slope2)
Le calcul non emballé – élastique vers le haut :
steep-slope
? clamp(Yf, slope1, max(Yb, slope2))
: max(clamp(Yf, slope1, Ya), slope2)
Combiné, avec enveloppement max en option (si screen-max Xu
Il est établi):
Xu
? steep-slope
? clamp(Yf, slope1, clamp(Yb, slope2, Yu))
: max(clamp(Yf, slope1, Ya), Yu)
: steep-slope
? clamp(Yf, slope1, max(Yb, slope2))
: max(clamp(Yf, slope1, Ya), slope2)
Ainsi, nous avons construit la structure de base de la formule. Maintenant, nous plongeons un peu plus profondément.
Calculer les valeurs manquantes
Voyons quelles valeurs nous obtenons comme argument et lesquelles nous devons calculer maintenant :
steep-slope
? clamp(Yf, slope1, clamp(Yb, slope2, Yu))
: max(clamp(Yf, slope1, Ya), Yu)
Pente raide
Ya = Yb = 22px
Yf = 16px
pente1 = mfa
pente2 = Premier
Yu

steep-slope
Vérifiez si la penteYf → Ya
est au-dessus de la penteYb → Yu
(m = 1) :
((Ya - Yf) / (Xa - Xf)) * 100 > 1
Mfa
Interpolation linéaire, y compris calcul de penteYf → Ya
:
Yf + (Ya - Yf) * (100vw - Xf) / (Xa - Xf)
Mbu
Interpolation linéaire entreYb
OuiYu
(pente m = 1):
100vw / Xb * Yb
Yu
Calcul de la position deYu
:
(Xu / Xb) * Yb
mets le tout ensemble
Xu
? ((Ya - Yf) / (Xa - Xf)) * 100 > 1
? clamp(Yf, Yf + (Ya - Yf) * (100vw - Xf) / (Xa - Xf), clamp(Yb, 100vw / Xb * Yb, (Xu / Xb) * Yb))
: max(clamp(Yf, Yf + (Ya - Yf) * (100vw - Xf) / (Xa - Xf), Ya), (Xu / Xb) * Yb)
: ((Ya - Yf) / (Xa - Xf)) * 100 > 1
? clamp(Yf, Yf + (Ya - Yf) * (100vw - Xf) / (Xa - Xf), max(Yb, 100vw / Xb * Yb))
: max(clamp(Yf, Yf + (Ya - Yf) * (100vw - Xf) / (Xa - Xf), Ya), 100vw / Xb * Yb)
Nous ferions mieux de stocker certains calculs dans des variables :
steep-slope = ((Ya - Yf) / (Xa - Xf)) * 100 > 1
slope1 = Yf + (Ya - Yf) * (100vw - Xf) / (Xa - Xf)
slope2 = 100vw / Xb * Yb
Yu = (Xu / Xb) * Yb
Xu
? steep-slope
? clamp(Yf, slope1, clamp(Yb, slope2, Yu))
: max(clamp(Yf, slope1, Ya), Yu)
: steep-slope
? clamp(Yf, slope1, max(Yb, slope2))
: max(clamp(Yf, slope1, Ya), slope2)
inclure le rapport d’aspect
Puisque nous voyons maintenant comment le chat saute, nous nous régalons d’un autre cookie. Dans le cas d’un format extrêmement large, par exemple un appareil mobile, nous souhaitons à nouveau réduire les tailles. C’est plus agréable et plus lisible ainsi.

Et si nous pouvions inclure le rapport d’aspect dans nos calculs ? Dans cet exemple, nous voulons réduire les tailles lorsque l’écran est plus large que le format d’image 16:9.
aspect-ratio = 16 / 9
screen-factor = min(100vw, 100vh * aspect-ratio)
Dans les deux interpolations de pente, nous remplaçons simplement 100vw
avec le nouveau facteur d’écran.
slope1 = Yf + (Ya - Yf) * (screen-factor - Xf) / (Xa - Xf)
slope2 = screen-factor / Xb * Yb
Alors finalement ça y est. Voyons maintenant toute la formule magique.
Formule
screen-factor = min(100vw, 100vh * aspect-ratio)
steep-slope = ((Ya - Yf) / (Xa - Xf)) * 100 > 1
slope1 = Yf + (Ya - Yf) * (screen-factor - Xf) / (Xa - Xf)
slope2 = screen-factor / Xb * Yb
Yu = (Xu / Xb) * Yb
Xu
? steep-slope
? clamp(Yf, slope1, clamp(Yb, slope2, Yu))
: max(clamp(Yf, slope1, Ya), Yu)
: steep-slope
? clamp(Yf, slope1, max(Yb, slope2))
: max(clamp(Yf, slope1, Ya), slope2)

Fonction
Nous pouvons maintenant intégrer la formule dans notre configuration. Dans cet article, nous verrons comment l’implémenter dans Sass. Les deux fonctions d’assistance s’assurent que nous produisons correctement les valeurs rem (je n’entrerai pas dans les détails). Nous définissons ensuite les points d’ancrage et le rapport d’aspect comme des constantes (respectivement, des variables Sass). Enfin, nous remplaçons les points de coordonnées de notre formule par des noms de variables et la FabUnit est prête à l’emploi.
_fab-unit.scss
@use "sass:math";
/* Helper functions */
$rem-base: 10px;
@function strip-units($number) {
@if (math.is-unitless($number)) {
@return $number;
} @else {
@return math.div($number, $number * 0 + 1);
}
}
@function rem($size){
@if (math.compatible($size, 1rem) and not math.is-unitless($size)) {
@return $size;
} @else {
@return math.div(strip-units($size), strip-units($rem-base)) * 1rem;
}
}
/* Default values fab-unit ? */
$screen-min: 375;
$screen-opt-start: 1024;
$screen-opt-end: 1440;
$screen-max: 2000; // $screen-opt-end | int > $screen-opt-end | false
$aspect-ratio: math.div(16, 9); // smaller values for larger aspect ratios
/* Magic function fab-unit ? */
@function fab-unit(
$size-min,
$size-opt,
$screen-min: $screen-min,
$screen-opt-start: $screen-opt-start,
$screen-opt-end: $screen-opt-end,
$screen-max: $screen-max,
$aspect-ratio: $aspect-ratio
) {
$screen-factor: min(100vw, 100vh * $aspect-ratio);
$steep-slope: math.div(($size-opt - $size-min), ($screen-opt-start - $screen-min)) * 100 > 1;
$slope1: calc(rem($size-min) + ($size-opt - $size-min) * ($screen-factor - rem($screen-min)) / ($screen-opt-start - $screen-min));
$slope2: calc($screen-factor / $screen-opt-end * $size-opt);
@if $screen-max {
$size-max: math.div(rem($screen-max), $screen-opt-end) * $size-opt;
@if $steep-slope {
@return clamp(rem($size-min), $slope1, clamp(rem($size-opt), $slope2, $size-max));
} @else {
@return clamp(clamp(rem($size-min), $slope1, rem($size-opt)), $slope2, $size-max);
}
} @else {
@if $steep-slope {
@return clamp(rem($size-min), $slope1, max(rem($size-opt), $slope2));
} @else {
@return max(clamp(rem($size-min), $slope1, rem($size-opt)), $slope2);
}
}
}
Comment utiliser FabUnit ?
Le travail est fait, maintenant c’est simple. Le guide de style de notre exemple peut être mis en œuvre en un rien de temps :

Nous lisons les valeurs associées dans le guide de style et les transmettons à FabUnit en tant qu’arguments : fab-unit(16, 22)
.
style.scss
@import "fab-unit";
/* overwrite default values ? */
$screen-max: 1800;
/* Style guide variables fab-unit ? */
$fab-font-size-body: fab-unit(16, 22);
$fab-font-size-body-small: fab-unit(14, 16);
$fab-font-size-h1: fab-unit(60, 160);
$fab-font-size-h2: fab-unit(42, 110);
$fab-font-size-h3: fab-unit(28, 60);
$fab-space-s: fab-unit(20, 30);
$fab-space-m: fab-unit(40, 80);
$fab-space-l: fab-unit(60, 120);
$fab-space-xl: fab-unit(80, 180);
/* fab-unit in action ? */
html {
font-size: 100% * math.div(strip-units($rem-base), 16);
}
body {
font-size: $fab-font-size-body;
}
.wrapper {
max-width: rem($screen-max);
margin-inline: auto;
padding: $fab-space-m;
}
h1 {
font-size: $fab-font-size-h1;
border-block-end: fab-unit(2, 10) solid plum;
}
…
p {
margin-block: $fab-space-s;
}
…
/* other use cases for calling fab-unit ? */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(fab-unit(200, 500), 1fr));
gap: $fab-space-m;
}
.thing {
flex: 0 0 fab-unit(20, 30);
height: fab-unit(20, 36, 660, 800, 1600, 1800); /* min, opt, … custom anchor points */
}
Maintenant, nous pouvons tracer la ligne de réponse en appelant fab-unit()
et en spécifiant seulement deux tailles, la minimale et l’optimale. Nous pouvons contrôler les tailles de police, les rembourrages, les marges et l’espacement, les hauteurs et les largeurs, et même, si nous le voulons, définir des colonnes de grille et des mises en page flexibles avec. Nous pouvons également déplacer localement les points d’ancrage prédéfinis.

Jetons un coup d’œil à la sortie compilée :
…
font-size: clamp(clamp(1.3rem, 1.3rem + 2 * (min(100vw, 177.7777777778vh) - 37.5rem) / 649, 1.5rem), min(100vw, 177.7777777778vh) / 1440 * 15, 2.0833333333rem);
…
Et la sortie calculée :
font-size: 17.3542px
problèmes d’accessibilité
Pour assurer une bonne accessibilité, je recommande de tester dans chaque cas si toutes les tailles sont suffisamment extensibles. Les arguments avec une grande différence peuvent ne pas se comporter comme souhaité. Pour plus d’informations à ce sujet, vous pouvez consulter l’article « Responsive Type and Zoom » d’Adrian Roselli.
conclusion
Nous avons maintenant créé une fonction qui fait tout le travail pour nous. Il prend une valeur minimale et optimale et renvoie un calcul à notre propriété CSS, en tenant compte de la largeur d’écran, du rapport d’aspect et des points d’ancrage spécifiés, une formule unique qui pilote l’ensemble du projet. Aucune requête multimédia, aucun point d’arrêt, aucune rupture de mise en page.
La FabUnit présentée ici est basée sur ma propre expérience et est optimisée pour la plupart de mes projets. Je gagne beaucoup de temps et je suis satisfait du résultat. Il se peut que vous et votre équipe ayez une approche différente et que vous ayez donc d’autres exigences pour une FabUnit. Ce serait bien si vous pouviez maintenant créer votre propre FabUnit en fonction de vos besoins.
Je serais heureux si mon approche vous inspirait de nouvelles idées. Je serais honoré si vous utilisiez directement le package FabUnit npm de cet article pour vos projets.
Merci! ??
LIENS DES UNITÉS FAB
Merci Eli, Roman, Patrik, Fidi.

(Oui oui)