Une fois n’est pas coutume, on va partir d’un exemple issu de notre fil rouge. Regardez le code suivant :
1 2 3 4 5 6 7 8 9 | #presentation { background-image: url('img/bg-presentation.jpg'); } #production { background-image: url('img/bg-production.jpg'); } #contact { background-image: url('img/bg-contact.jpg'); } |
Vous remarquez qu’on a répété trois fois le même code en ne changeant qu’un mot à chaque fois.
Stockons les différentes variantes dans une liste :
1 | $sections: "presentation", "production", "contact"; |
Et maintenant, comment fait-on ? On utilise une boucle, évidemment !
La boucle @for
Un boucle permet de répéter un certain nombre de fois un même bout de code, en changeant une valeur à chaque fois. Il existe 3 familles de boucles dans le langage SCSS (nous n’en verrons que deux). La première, c’est la boucle @for. Observons le code suivant :
1 2 3 4 5 | @for $i from 1 through 3 { #section-#{$i} { background-image: url('img/bg-' + $i + '.png'); } } |
On obtient le CSS suivant :
1 2 3 4 5 6 7 8 9 | #section-1 { background-image: url("img/bg-1.png"); } #section-2 { background-image: url("img/bg-2.png"); } #section-3 { background-image: url("img/bg-3.png"); } |
Comme vous pouvez le voir, la boucle @for
a répété trois fois le bloc de code, en changeant la valeur de $i
à chaque itération (ou répétition de la boucle). Cette variable (appelée indice de la boucle) à pris tour à tour les valeurs 1, 2 puis 3 (tous les entiers entre 1 et 3, from 1 through 3
). Remarquez que ici, l’interpolation et la concaténation m’ont été très utiles.
Bon, c’est très bien tout cela, mais on n’a pas obtenu le code du départ. Alors oui, on pourrait changer les ids de nos sections dans le HTML et les noms de nos images, mais ce serait de la triche. Non, on va plutôt utiliser notre liste $sections
, et une fonction que nous avons vu il y a peu : nth()
. Et il faut bien dire qu’elle ne révèle toute son utilité qu’avec les boucles :
1 2 3 4 5 6 | $sections: "presentation", "production", "contact"; @for $i from 1 through length($sections) { #section-#{nth($section, $i)} { background-image: url('img/bg-' + nth($section, $i) + '.png'); } } |
Et voilà ! On obtient à nouveau un code qui se sert des noms de images et de nos ids. Remarquez que, pour plus de flexibilité, j’ai utilisé length()
pour indiquer la fin de la boucle. Ainsi, si on a une nouvelle section à ajouter, on aura juste à modifier la liste et la boucle sera toujours valable.
Deux petites choses à savoir aussi sur la boucle @for
(mais dont l’utilité est limitée) :
- on peut compter à l’envers :
@for $i from 4 through 1
($i
vaudra 4, 3, 2, puis 1). - on peut remplacer
through
parto
: dans ce cas, Sass n’inclut pas la dernière valeur (dans notre exemple,$i
vaudra 1 puis 2 mais pas 3).
La boucle @each
La boucle @for est formidable. Mais il y a encore mieux. Les boucles sont la plupart du temps utilisées pour parcourir des listes (c’est d’ailleurs ce qu’on a fait). Les concepteurs de Sass ont donc ajouté à notre attirail une autre boucle, dédiée spécifiquement à cette utilisation. La boucle @each, comme son nom l’indique, se répète pour CHAQUE item d’une liste. Elle s’utilise comme ceci :
1 2 3 4 5 6 | $sections: "presentation", "production", "contact"; @each $section in $sections { #section-#{$section} { background-image: url('img/bg-' + nth($section, $i) + '.png'); } } |
La première ligne se lit ainsi : la variable $section
vaudra tour à tour chaque valeur contenue dans la liste $sections
. On obtient donc le même code que précédemment.
Vous vous rappelez des listes multidimensionnelles ? Elles sont aussi parcourables avec @each
, c’est ce qu’on appelle l’affectation multiple. À chaque itération, on peut accéder à tous les éléments d’une des « sous-listes », comme ceci :
1 2 3 4 5 6 | // Ce bout de code permet de gérer en un rien de temps le rythme vertical de vos titres. @each $titre, $fonte in (h1, 2.5rem), (h2, 2rem), (h3, 1.5rem) { #{$titre} { font-size: $fonte; } } |
Le code CSS généré est le suivant :
1 2 3 4 5 6 7 8 9 | h1 { font-size: 2.5rem; } h2 { font-size: 2rem; } h3 { font-size: 1.5rem; } |
Personnellement, je trouve plus simple et plus lisible d’utiliser la fonction zip()
:
1 2 3 4 5 6 7 | $titres: h1, h2, h3; $fontes: 2.5rem, 2rem, 1.5rem; @each $titre, $fonte in zip($titres, $fontes) { #{$titre} { font-size: $fonte; } } |
Voilà, c’est tout pour @each
, même si nous en reparlerons lorsqu’il sera question des maps.
Mini-TP : La gestion d'une sprite
Énoncé
Je vous propose de finir ce chapitre avec un petit exercice où les boucles nous seront particulièrement utiles : l’utilisation d’une sprite d’images. Une sprite, si vous ne savez pas ce que c’est, c’est une grande image sur laquelle on regroupe toutes les icônes, tous les smilleys, tous les pictogrammes utilisés dans un design. Comme souvent, le but recherché est de réduire la charge pour le serveur et le temps de chargement pour les visiteurs (plutôt que d’effectuer une trentaine de requêtes sur les images, on n’en a plus qu’une seule à effectuer). Ensuite, on se débrouille avec des background-position
pour sélectionner la bonne image. Comme c’est assez fatigant à effectuer manuellement, il existe des outils clef-en-main qui génèrent directement la sprite et le code CSS correspondant. Mais je vous propose de partir sur quelque chose de simple et de tout faire avec une boucle. Le but est d’obtenir ceci :
Voici le code HTML (il n’a rien d’exceptionnel) :
1 2 3 4 5 | <a class="button icon-settings">Settings</a> <a class="button icon-watch">Watch</a> <a class="button icon-power">Power</a> <a class="button icon-fullscreen">Full Screen</a> <a class="button icon-reduce">Reduce</a> |
Voici notre sprite :
Et voici les styles de base de nos boutons (ce n’est pas franchement le sujet de l’exercice) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .button { font-family: sans-serif; border: none; padding: 10px 10px 10px 52px; // On laisse de la place à gauche background-color: #fe0; position: relative; // Pour pouvoir placer l'icône en absolute à l'intérieur display: inline-block; height: 32px; line-height: 32px; &:hover { background-color: lighten(#fe0, 20%); // C'est plus sympa, non ? } } |
Maintenant, à vous de jouer ! Il suffit de chipoter un peu avec .button::before
, une boucle et background-position
et le tour est joué.
Correction
Voici ma correction de l’exercice :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | .button { font-family: sans-serif; border: none; padding: 10px 10px 10px 52px; // On laisse de la place à gauche background-color: #fe0; position: relative; // Pour pouvoir placer l'icône en absolute à l'intérieur display: inline-block; height: 32px; line-height: 32px; &:hover { background-color: lighten(#fe0, 20%); // C'est plus sympa, non ? } &::before { content: ' '; display: block; position: absolute; top :10px; left: 10px; width: 32px; height: 32px; background-image: url(sprite.png); } } $icons: 'settings', 'watch', 'power', 'fullscreen', 'reduce'; @for $i from 1 through 5 { .icon-#{nth($icons, $i)}::before { $pos: -32px * ($i - 1); // On commence à 0 et on finit à 32*4px background-position-x: $pos; } } |
J’ai surligné la partie qui nous intéresse vraiment. On a donc une liste qui contient les noms des icônes dans le bon ordre et une boucle @for
qui parcourt la liste (avec la fonction nth()
), insère le nom dans le sélecteur (avec une interpolation qui va bien) et la valeur de l’indice $i
sert à calculer la position de la sprite. On aurait aussi pu parcourir la liste directement avec une boucle @each
et se servir de la fonction (index()
) pour retrouver la valeur de $i
, mais ça me semblait moins évident.
Voilà pour ce petit exercice. Comme dit précédemment, on préfèrera souvent utiliser un outil externe1 qui nous génère la sprite directement et le code (S)CSS qui va avec. En effet, on avait ici des icônes de la même taille alignées, ce qui est rarement le cas en conditions réelles, où il s’agit de gérer des sprites plus complexes. Voici par exemple celle utilisée par votre site préféré :
- La boucle
@for
est utile pour répéter plusieurs fois un même code en changeant uniquement un nombre et s’utilise ainsi :@for $i from 1 through 4 {...}
. - La boucle
@each
permet de répéter un même bout de code en parcourant une liste d’items, on s’en sert comme ceci :@each $aliment in frites, tomate, yaourt, raviolis {...}
.
Je ne vous ai volontairement pas parlé de la boucle @while
, qui me semble moins utile que les deux autres. Sachez que c’est une boucle qui se répète tant qu’une condition est vraie. Si vous pensez en avoir besoin un jour, je vous invite à consulter la doc Sass.