Les mixins (2/2)

Les mixins, ça continue ! On va parler de quelques trucs au sujet des mixins qui peuvent vous faciliter la vie.

Valeurs par défaut, arguments nommés et listes d'arguments

Tout d’abord, parlons arguments. Les arguments, on l’a vu, permettent de changer certaines valeurs à l’intérieur de notre mixin. Il arrive cependant qu’un mixin soit souvent utilisé avec les mêmes valeurs (mais pas toujours, sinon on ne voit pas pourquoi on a créé des arguments). Dans ce cas on peut attribuer des valeurs par défaut à nos arguments. Si l’on fournit une valeur à l’inclusion, le mixin l’utilisera, sinon, il se servira de la valeur par défaut. Une valeur par défaut se déclare à la création du mixin, comme ceci :

1
2
3
@mixin button($color: $dark, $background-color: $color, $hover-color: $color-hover) {
    ...
}

Dans ce cas, il est possible d’omettre les valeurs de tous les arguments, ou de certains arguments.

Ainsi :

1
2
3
4
5
6
7
@include button;
// Correspondra à :
@include button($dark, $color, $color-hover);
// Tandis que :
@include button(#000);
// Correspondra à :
@include button(#000, $color, $color-hover);

Vous remarquez que lorsque seulement certaines valeurs sont données à @include, Sass "remplit" les arguments de gauche à droite : on ne peut pas renseigner une valeur pour $hover-color, sans donner au préalable une valeur à $color. L’ordre dans lequel on déclare les arguments dans l’entête du mixin a donc une importance.

Une autre syntaxe permet cependant de contourner cette limite, les arguments nommés. En effet, il est possible d’utiliser les noms des mixins lors de l’inclusion :

1
2
3
@include button($color:#000, $background-color:#eee, $hover-color: #ccc);
@include button($hover-color: #eee, $color: #000);
@include button($hover-color: #ccc);

Analysons ces trois lignes :

  • Dans la première, les arguments $color, $background-color et $hover-color prennent respectivement les valeurs #000, #eee et #ccc.
  • Dans la deuxième, les arguments $color et $hover-color prennent respectivement les valeurs #000 et #eee, tandis que $background-color garde sa valeur par défaut (si on n’avait pas donné de valeur par défaut à la création du mixin, Sass aurait signalé une erreur).
  • Dans la troisième, on donne juste la valeur #ccc à $hover-color, les autres arguments se voient attribués leurs valeurs par défaut.

Pourquoi utiliser les arguments nommés (keyword arguments en anglais) ? Certes, c’est plus long, mais on gagne probablement un peu en lisibilité. De plus, on ne se pose plus la question de l’ordre des arguments : cela permet, par exemple, de n’attribuer une valeur spécifique qu’au dernier argument, comme on le fait ici. Cela n’aurait pas été possible sans nommer les arguments.

Dernière chose à voir concernant les arguments : le cas particulier des listes. Imaginons que l’on souhaite rajouter un argument $font à notre mixin de bouton, pour pouvoir paramétrer la police utilisée.

1
2
3
4
@mixin button($color, $background-color, $hover-color, $font) {
    font-family: $font;
    ...
}

Habituellement, on utilise donne une liste de polices. Si on passe notre variable $head-font (qui est une liste de chaines de caractères) à l’inclusion, tout se passe comme prévu :

1
@include button($dark, $light, $ultra-light, $head-font);

Cependant, imaginons un cas exceptionnel pour lequel notre liste de polices n’est pas stockée dans une variable, comment les passer à nos mixins ?

1
@include button($dark, $light, $ultra-light, "Source Sans Pro", "Segoe UI", "Trebuchet MS", Helvetica, "Helvetica Neue", Arial, sans-serif);

Si vous essayez la ligne précédente, vous allez obtenir une erreur, et c’est logique : Sass attend 4 arguments, et il en reçoit 10. Deux solutions s’offrent en fait à nous. On peut tout d’abord encadrer notre liste de polices par des parenthèses pour faire comprendre à Sass qu’il s’agit d’un seul argument :

1
@include button($dark, $light, $ultra-light, ("Source Sans Pro", "Segoe UI", "Trebuchet MS", Helvetica, "Helvetica Neue", Arial, sans-serif));

Sinon, on peut utiliser la syntaxe des arguments variables. En ajoutant ... après le nom du dernier argument lors de la création du mixin, on indique à Sass que cet argument devra contenir la liste de toutes les valeurs passées au mixin qui restent. On ne peut logiquement avoir qu’un seul argument variable par mixin (vu qu’il prend toutes les valeurs restantes). Ainsi, le code suivant marche :

1
2
3
4
5
6
7
8
@mixin button($color, $background-color, $hover-color, $font...) {
    font-family: $font;
    ...
}

#bouton {
  @include button($dark, $light, $ultra-light, "Source Sans Pro", "Segoe UI", "Trebuchet MS", Helvetica, "Helvetica Neue", Arial, sans-serif);
}

Dans cet exemple $font contiendra la liste de toutes valeurs restantes à partir de "Source Sans Pro". Cette syntaxe est particulièrement pratique pour des mixins dont on ne connait pas le nombre d’arguments. C’est le cas par exemple des mixins utilisés pour préfixer certaines propriétés CSS3 comme box-shadow, transition ou animation :

1
2
3
4
5
6
// Exemple issu de la doc Sass
@mixin box-shadow($shadows...) {
    -moz-box-shadow: $shadows;
    -webkit-box-shadow: $shadows;
    box-shadow: $shadows;
}

Les mixins pour préfixer des règles CSS3 ont probablement été l’une des raisons du succès de Sass, à travers des bibliothèques de mixins prêts-à-l’emploi. Aujourd’hui, ce genre de mixin a tendance à être remplacé par des outils comme Autoprefixer.

Passer un bloc de contenu à un mixin

Dans le chapitre sur les variables, je vous ai montré comment l’interpolation pouvait être utilisée avec les media-queries. Un autre moyen de gérer ces dernières passe par l’utilisation de mixins. Observez par exemple ce code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@mixin tablette {
    @media (max-width: 950px) {
        @content;
    }
}

h1{
    font-size: 3.5rem;
    @include tablette {
        font-size: 2.5rem;
    }
}

Deux détails à remarquer :

  • le mixin tablette contient la directive @content, que nous n’avons pas encore vue ;
  • l’inclusion du mixin est suivie d’un bloc de code entre accolades.

En fait, lorsque Sass voit un @content à l’intérieur d’un mixin, il s’attend à ce qu’on lui passe un bloc de code lors de l’inclusion. Ce bloc de code va prendre la place de la directive @content partout où elle est présente dans le mixin inclus. Dans notre exemple, le code CSS généré sera :

1
2
3
4
5
6
7
8
h1 {
    font-size: 3.5rem;
}
@media (max-width: 950px) {
    h1 {
        font-size: 2.5rem;
    }
}

Comme vous pouvez le constater, @content a effectivement été remplacé par font-size: 2.5rem. Si on avait passé plusieurs règles à notre mixin, elles auraient toutes été insérées à l’emplacement de @content.

Vous l’aurez compris, on peut tout à fait remplacer nos variables $small-screen et $large-screen par des mixins et employer @include à la place de l’interpolation. On obtiendrait ce genre de code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@mixin small-screen {
    @media screen and (max-width: 540px) {
        @content;
    }
}
@mixin large-screen {
    @media screen and (min-width: 740px) {
        @content;
    }
}

Les deux techniques se valent à mon avis pour ce qui est des media-queries. À vous de choisir celle qui vous convient le mieux.

Bonne pratique : Arguments & type null

Le contenu de cette sous-partie s’inspire de l’article Smarter Sass Mixins with Null(en) de Guil Hernandez.

Imaginons maintenant que l’on veuille un bouton qui ne change pas de couleur au hover. Le seul moyen qu’on a d’obtenir cela consiste à passer la même valeur pour $background-color et $hover-color. Sauf que c’est un peu idiot, parce que le code concernant $hover-color va quand même être généré, alors qu’il ne sert à rien.

Il faudrait pouvoir dire à Sass que $hover-color est optionnel et que s’il n’est pas renseigné, on doit ignorer la règle concernant le hover. Et ça tombe bien, parce qu’on peut faire cela avec un argument de type null.

Je l’avais très rapidement évoqué dans le chapitre sur les variables : le type null permet d’indiquer à Sass qu’une variable ne contient rien. La seule valeur que peut prendre une variable de ce type est null :

1
$variable: null;

Vous vous demandez sans doute à quoi ça peut bien servir. En fait, il faut savoir que lorsqu’une propriété reçoit la valeur null, elle est ignorée par Sass et n’apparait pas dans le code compilé. Ainsi, le code suivant n’apparaitra pas après compilation :

1
2
3
4
$variable: null;
p {
    color: $variable;
}

Si un argument reçoit la valeur null et qu’on passe cet argument à une propriété, la propriété est donc exclue du ficher CSS. Mieux, si on attribue null comme valeur par défaut à un argument et qu’on passe cet argument à une propriété à l’intérieur du mixin, alors la propriété sera exclue du fichier CSS à moins que l’on donne explicitement une valeur à l’argument lors de l’@include. Modifions donc la valeur par défaut de $hover-color :

1
2
3
@mixin button($color, $background-color, $hover-color: null) {
    ...
}

Désormais, si on inclut notre mixin sans préciser de valeur pour $hover-color, on obtiendra un bouton sans hover :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.my-button {
    font-family: "Grand Hotel", serif;
    font-size: 1.25rem;
    text-decoration: none;
    text-align: center;
    padding: .5rem;
    width: 12.5rem;
    border: none;
    display: block;
    float: none;
    margin: .5rem auto 0;
    background-color: #ff0;
    color: #222;
}
@media screen and (min-width: 740px) {
    .my-button {
        display: inline-block;
        float: right;
    }
}

La valeur null est donc particulièrement utile pour rendre certains arguments optionnels sans générer de code inutile.

Petite limite quand même : cette technique ne marche pas si on effectue des calculs avec cet argument. En effet, toute opération mathématique impliquant la valeur null provoquera une erreur.


En résumé

  • On peut définir des valeurs par défaut pour les arguments de notre mixin, comme ceci :

    1
    @mixin nom_du_mixin($argument: "Valeur par défaut") {...}
    
  • Il est possible de nommer explicitement les arguments à l’inclusion du mixin :

    1
    @include nom_du_mixin($nom_argument: valeur);
    
  • Si le nom du dernier argument déclaré dans la directive @mixin est suivi de ..., alors cet argument prendra pour valeur la liste de toutes les valeurs restantes lors de l’@include.

  • On peut passer un bloc de code à un mixin. Ce code se place entre accolades {} après les arguments lors de l’inclusion du mixin. Lors de la création du mixin, on indique où le bloc doit être placé avec la commande @content;. Cette technique peut servir afin de gérer les media-queries globalement avec des mixins.
  • En attribuant à un mixin la valeur par défaut null, on rend cet argument optionnel : les propriétés qui reçoivent cet argument seront ignorées par Sass lors de la compilation et n’apparaîtront pas dans le fichier CSS généré.