Catégories
Astuces et Design

Quand Sass et les nouvelles fonctionnalités CSS entrent en collision

Récemment, CSS a ajouté de nombreuses nouvelles fonctionnalités intéressantes telles que des propriétés personnalisées et de nouvelles fonctions. Bien que ces choses puissent nous faciliter la vie, elles peuvent aussi finir par interagir avec des préprocesseurs, comme Sass, de façon amusante.

Donc, ce sera un article sur les problèmes que j'ai rencontrés, comment je les contourne et pourquoi je trouve toujours Sass nécessaire ces jours-ci.

Les erreurs

Si vous avez joué avec le nouveau min() et max() fonctions, vous avez peut-être rencontré un message d'erreur comme celui-ci lorsque vous travaillez avec différentes unités: "Unités incompatibles: vh et em. "

Capture d'écran. Affiche l'erreur `Incompatible units: 'em' et 'vh'` lorsque vous essayez de définir` width: min (20em, 50vh) `.
Une erreur lors de l'utilisation de différents types d'unités dans le min()/ max() fonction

Ceci est dû au fait Sass a son propremin() fonction, et ignore le CSS min() fonction. De plus, Sass ne peut effectuer aucune sorte de calcul en utilisant deux valeurs avec des unités qui n'ont pas de relation fixe entre elles.

Par exemple, cm et in les unités ont une relation fixe entre elles, donc Sass peut comprendre quel est le résultat de min(20in, 50cm) et ne génère pas d'erreur lorsque nous essayons de l'utiliser dans notre code.

Il en va de même pour les autres unités. Les unités angulaires, par exemple, ont toutes une relation fixe entre elles: 1turn, 1rad ou 1grad toujours calculer de la même façon deg valeurs. De même pour 1s ce qui est toujours 1000ms, 1kHz ce qui est toujours 1000Hz, 1dppx ce qui est toujours 96dpi, et 1in ce qui est toujours 96px. C'est pourquoi Sass peut convertir entre eux et les mélanger dans des calculs et des fonctions internes comme la sienne min() fonction.

Mais les choses se brisent lorsque ces unités n'ont pas de relation fixe entre elles (comme dans le cas précédent avec em et vh unités).

Et ce ne sont pas seulement des unités différentes. Essayer d'utiliser calc() à l'intérieur min() entraîne également une erreur. Si j'essaye quelque chose comme calc(20em + 7px), l'erreur que j'obtiens est "calc(20em + 7px) n'est pas un nombre pour min. "

Capture d'écran. Montre que `` calc (20em + 7px) 'n'est pas un nombre pour l'erreur' min '' lorsque vous essayez de définir `width: min (calc (20em + 7px), 50vh)`.
Une erreur lors de l'utilisation de différentes valeurs d'unité avec calc() imbriqué dans le min()fonction

Un autre problème se pose lorsque nous voulons utiliser une variable CSS ou le résultat d'une fonction mathématique CSS (comme calc(), min() ou max()) dans un filtre CSS comme invert().

Dans ce cas, on nous dit que «$color: 'var(--p, 0.85) n'est pas une couleur pour invert. "

Capture d'écran. Indique que `$ color: 'var (- p, 0.85)' n'est pas une couleur pour l'erreur 'invert'` lors de la tentative de définition de` filter: invert (var (- p, .85))'.
var() dans filter: invert() Erreur

La même chose se produit pour grayscale(): "$color: «calc(.2 + var(--d, .3))«N'est pas une couleur pour grayscale. "

Capture d'écran. Montre que `$ color: 'calc (.2 + var (- d, .3))' n'est pas une couleur pour l'erreur 'niveaux de gris'` lors de la tentative de définition de` filter: grayscale (calc (.2 + var (- -d, .3))) `.
calc() dans filter: grayscale() Erreur

opacity() provoque le même problème: "$color: «var(--p, 0.8)«N'est pas une couleur pour opacity. "

Capture d'écran. Indique que `$ color: 'var (- p, 0.8)' n'est pas une couleur pour l'erreur 'opacity'` lorsque vous essayez de définir` filter: opacity (var (- p, 0.8))'.
var() dans filter: opacity() Erreur

Cependant, d’autres filter fonctions – y compris sepia(), blur(), drop-shadow(), brightness(), contrast() et hue-rotate()– tout fonctionne très bien avec les variables CSS!

Il s'avère que ce qui se passe est similaire à la min() et max() problème. Sass n'a pas intégré sepia(), blur(), drop-shadow(), brightness(), contrast(), hue-rotate() fonctions, mais il a sa propre grayscale(), invert() et opacity() fonctions, et leur premier argument est un $color valeur. Puisqu'il ne trouve pas cet argument, il renvoie une erreur.

Pour la même raison, nous rencontrons également des problèmes lorsque nous essayons d'utiliser une variable CSS qui répertorie au moins deux hsl()ou hsla() valeurs.

Capture d'écran. Affiche le `nombre incorrect d'arguments (2 pour 3) pour l'erreur 'hsl'` lors de la tentative de définition de` color: hsl (9, var (- sl, 95%, 65%)) `.
var() dans color: hsl() Erreur.

D'un autre côté, color: hsl(9, var(--sl, 95%, 65%)) est CSS parfaitement valide et fonctionne très bien sans Sass.

La même chose se produit exactement avec le rgb()et rgba() les fonctions.

Capture d'écran. Montre que `$ color: 'var (- rgb, 128, 64, 64)' n'est pas une couleur pour l'erreur 'rgba'` lorsque vous essayez de définir` color: rgba (var (- rgb, 128, 64, 64 ), .7) ».
var() dans color: rgba() Erreur.

De plus, si nous importons Compass et essayons d'utiliser une variable CSS dans un linear-gradient() ou à l'intérieur d'un radial-gradient(), nous obtenons une autre erreur, même si nous utilisons des variables à l'intérieur conic-gradient() fonctionne très bien (c'est-à-dire si le navigateur le prend en charge).

Capture d'écran. Affiche le Au moins deux arrêts de couleur sont requis pour une erreur de dégradé linéaire lorsque vous essayez de définir l'arrière-plan: dégradé linéaire (var (- c, rose), or).
var() dans background: linear-gradient() Erreur.

En effet, Compass est livré avec linear-gradient() et radial-gradient() fonctions, mais n'a jamais ajouté de conic-gradient() une.

Dans tous ces cas, les problèmes proviennent du fait que Sass ou Compass ont des fonctions portant le même nom et supposent que ce sont ce que nous avions l'intention d'utiliser dans notre code.

Drat!

La solution

L'astuce ici est de se rappeler que Sass est sensible à la casse, mais pas CSS.

Cela signifie que nous pouvons écrire Min(20em, 50vh)et Sass ne le reconnaîtra pas comme son propre min() fonction. Aucune erreur ne sera levée et c'est toujours du CSS valide qui fonctionne comme prévu. De même, l'écriture HSL()/ HSLA()/ RGB()/ RGBA() ou Invert() nous permet d'éviter les problèmes que nous avons examinés plus tôt.

Quant aux dégradés, je préfère généralement linear-Gradient() et radial-Gradient() juste parce que c'est plus proche de la version SVG, mais utiliser au moins une lettre majuscule fonctionne très bien.

Mais pourquoi?

Presque chaque fois que je tweete quelque chose lié à Sass, je reçois des conférences sur la façon dont il ne devrait pas être utilisé maintenant que nous avons des variables CSS. Je pensais aborder cela et expliquer pourquoi je ne suis pas d'accord.

Tout d'abord, même si je trouve les variables CSS extrêmement utiles et que je les ai utilisées pour presque tout au cours des trois dernières années, il est bon de garder à l'esprit qu'elles ont un coût de performance et ce traçage où quelque chose s'est mal passé dans un labyrinthe de calc() les calculs peuvent être pénibles avec nos DevTools actuels. J'essaie de ne pas en abuser pour éviter d'entrer dans un territoire où les inconvénients de leur utilisation l'emportent sur les avantages.

Capture d'écran. Montre comment les expressions `calc ()` sont présentées dans DevTools.
Pas vraiment facile de comprendre quel est le résultat de ces calc() expressions.

En général, s'il agit comme une constante, ne change pas d'élément à élément ou d'état à état (auquel cas les propriétés personnalisées sont définitivement la voie à suivre) ou ne réduit pas la quantité de CSS compilé (résoudre le problème de répétition créé par des préfixes), alors je vais utiliser une variable Sass.

Deuxièmement, les variables ont toujours été une très petite partie de la raison pour laquelle j'utilise Sass. Lorsque j'ai commencé à utiliser Sass fin 2012, c'était principalement pour le bouclage, une fonctionnalité que nous n'avons toujours pas en CSS. Bien que j'aie déplacé une partie de cette boucle vers un préprocesseur HTML (car cela réduit le code généré et évite d'avoir à modifier le HTML et le CSS plus tard), j'utilise toujours les boucles Sass dans de nombreux cas, comme la génération de listes de valeurs, listes d'arrêt à l'intérieur des fonctions de dégradé, listes de points à l'intérieur d'une fonction de polygone, listes de transformations, etc.

Voici un exemple. J'avais l'habitude de générer n Éléments HTML avec un préprocesseur. Le choix du préprocesseur importe moins, mais j'utiliserai Pug ici.

- let n = 12;

while n--
  .item

Ensuite, je définirais la $n variable dans le Sass (et il devrait être égal à celui du HTML) et boucler dessus pour générer les transformations qui positionneraient chaque élément:

$n: 12;
$ba: 360deg/$n;
$d: 2em;

.item {
  position: absolute;
  top: 50%; left: 50%;
  margin: -.5*$d;
  width: $d; height: $d;
  /* prettifying styles */

  @for $i from 0 to $n {
    &:nth-child(#{$i + 1}) {
      transform: rotate($i*$ba) translate(2*$d) rotate(-$i*$ba);
			
      &::before { content: '#{$i}' }
    }
  }
}

Cependant, cela signifiait que je devrais changer à la fois le Pug et le Sass lors du changement du nombre d'éléments, ce qui rend le code généré très répétitif.

Capture d'écran. Affiche la déclaration de transformation CSS, vraiment verbeuse, presque complètement identique, répétée pour chaque élément.
CSS généré par le code ci-dessus

Depuis, je suis passé à faire en sorte que Pug génère les indices en tant que propriétés personnalisées, puis les utilise dans le transform déclaration.

- let n = 12;

body(style=`--n: ${n}`)
  - for(let i = 0; i < n; i++)
    .item(style=`--i: ${i}`)
$d: 2em;

.item {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -.5*$d;
  width: $d;
  height: $d;
  /* prettifying styles */
  --az: calc(var(--i)*1turn/var(--n));
  transform: rotate(var(--az)) translate(2*$d) rotate(calc(-1*var(--az)));
  counter-reset: i var(--i);
	
  &::before { content: counter(i) }
}

Cela réduit considérablement le code généré.

Capture d'écran. Affiche le CSS généré, beaucoup plus compact, sans avoir presque exactement la même déclaration définie sur chaque élément séparément.
CSS généré par le code ci-dessus

Cependant, une boucle dans Sass est toujours nécessaire si je veux générer quelque chose comme un arc-en-ciel.

@function get-rainbow($n: 12, $sat: 90%, $lum: 65%) {
  $unit: 360/$n;
  $s-list: ();
	
  @for $i from 0 through $n {
    $s-list: $s-list, hsl($i*$unit, $sat, $lum)
  }
	
  @return $s-list
}

html { background: linear-gradient(90deg, get-rainbow()) }

Bien sûr, je pourrais le générer en tant que variable de liste à partir de Pug, mais cela ne profite pas de la nature dynamique des variables CSS et cela ne réduit pas la quantité de code qui est servi au navigateur, donc il n'y a aucun avantage à venir en dehors de ça.

Une autre grande partie de mon utilisation de Sass (et Compass) est liée aux fonctions mathématiques intégrées (telles que les fonctions trigonométriques), qui font maintenant partie des spécifications CSS, mais qui ne sont pas encore implémentées dans aucun navigateur. Sass ne vient pas non plus avec ces fonctions, mais Compass le fait et c'est pourquoi j'ai souvent besoin d'utiliser Compass.

Et, bien sûr, je pouvais écrire mes propres fonctions dans Sass. J'ai eu recours à cela au début, avant que Compass ne prenne en charge les fonctions trigonométriques inverses. J'avais vraiment besoin d'eux, alors j'ai écrit le mien basé sur la série Taylor. Mais Compass offre ce genre de fonctions de nos jours et elles sont meilleures et plus performantes que la mienne.

Les fonctions mathématiques sont extrêmement importantes pour moi car je suis un technicien, pas un artiste. Les valeurs de mon CSS résultent généralement de calculs mathématiques. Ce ne sont pas des nombres magiques ou quelque chose utilisé uniquement pour l'esthétique. Un exemple est la génération de listes de points de chemins de délimitation qui créent des polygones réguliers ou quasi-réguliers. Pensez au cas où nous voulons créer des choses comme des avatars non rectangulaires ou des autocollants.

Prenons un polygone régulier avec des sommets sur un cercle de rayon 50% de l'élément carré dont nous partons. Faire glisser le curseur dans la démo suivante nous permet de voir où les points sont placés pour différents nombres de sommets:

En le mettant en code Sass, nous avons:

@mixin reg-poly($n: 3) {
  $ba: 360deg/$n; // base angle
  $p: (); // point coords list, initially empty
	
  @for $i from 0 to $n {
    $ca: $i*$ba; // current angle
    $x: 50%*(1 + cos($ca)); // x coord of current point
    $y: 50%*(1 + sin($ca)); // y coord of current point
    $p: $p, $x $y // add current point coords to point coords list
  }
	
  clip-path: polygon($p) // set clip-path to list of points
}

Notez qu'ici, nous utilisons également le bouclage et des choses telles que les conditions et le modulo qui sont vraiment pénibles lorsque vous utilisez CSS sans Sass.

Une version légèrement plus évoluée de cela pourrait impliquer la rotation du polygone en ajoutant le même angle de décalage ($oa) à l'angle de chaque sommet. Cela peut être vu dans la démo suivante. Cet exemple lance un mixage d'étoiles qui fonctionne de la même manière, sauf que nous avons toujours un nombre pair de sommets et que chaque sommet impair est situé sur un cercle d'un rayon plus petit ($f*50%, où $f est sous-unitaire):

Nous pouvons également avoir des étoiles joufflues comme ceci:

Ou des autocollants avec intéressant border motifs. Dans cette démo particulière, chaque autocollant est créé avec un seul élément HTML et le border le motif est créé avec clip-path, boucle et mathématiques à Sass. Pas mal, en fait.

Un autre exemple sont ces arrière-plans de carte où le bouclage, l'opération modulo et les fonctions exponentielles fonctionnent ensemble pour générer les couches d'arrière-plan de pixel tramées:

Il se trouve que cette démo dépend également fortement des variables CSS.

Ensuite, vous utilisez des mixins pour éviter d'écrire les mêmes déclarations encore et encore lorsque vous stylisez des choses comme des entrées de plage. Différents navigateurs utilisent différents pseudo-éléments pour styliser les composants d'un tel contrôle, donc pour chaque composant, nous devons définir les styles qui contrôlent son apparence sur plusieurs pseudos.

Malheureusement, aussi tentant que cela puisse être de mettre cela dans notre CSS:

input::-webkit-slider-runnable-track, 
input::-moz-range-track, 
input::-ms-track { /* common styles */ }

… Nous ne pouvons pas le faire car cela ne fonctionne pas! L'ensemble des règles est supprimé si même l'un des sélecteurs n'est pas reconnu. Et comme aucun navigateur ne reconnaît les trois éléments ci-dessus, les styles ne sont appliqués dans aucun navigateur.

Nous devons avoir quelque chose comme ça si nous voulons que nos styles soient appliqués:

input::-webkit-slider-runnable-track { /* common styles */ }
input::-moz-range-track { /* common styles */ }
input::-ms-track { /* common styles */ }

Mais cela peut signifier beaucoup de styles identiques répétés trois fois. Et si nous voulons changer, disons, le background de la piste, nous devons le changer dans le ::-webkit-slider-runnable-track styles, dans le ::-moz-range-track styles et dans le ::-ms-track modes.

La seule solution sensée que nous ayons est d'utiliser un mixin. Les styles sont répétés dans le code compilé car ils doivent y être répétés, mais nous n'avons plus besoin d'écrire la même chose trois fois.

@mixin track() { /* common styles */ }

input {
  &::-webkit-slider-runnable-track { @include track }
  &::-moz-range-track { @include track }
  &::-ms-track { @include track }
}

Le résultat est: oui, Sass est encore très nécessaire en 2020.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *