Avez-vous déjà mis bien plus de temps que vous ne l'imaginiez pour modifier une feuille de style ? Vous êtes-vous déjà dis qu'une modification ne devrait pas être si périlleuse. Derrière un langage si simple à prendre en main se cache un langage qui ne nous offre pas les moyens d'affronter la complexité qui grandit en même temps que l'application.

Grâce à l'aide d'extensions comme Sass, le développement CSS devient tout de suite plus professionnel. On retrouve les concepts des autres langages (variables, méthodes, ...) et cela change radicalement la situation.

Comment faire la transition de nos feuilles de style CSS pour les rendre plus maintenables ? Soit les réécrire complètement. Soit suivre les refactorings présentés dans ce document pour progressivement arriver à notre objectif, tout en ayant à chaque étape, une feuille de style opérationnelle.

Bad Smells

Les Bad Smells sont fortement inspirées de celles présentes dans l'ouvrage référence de Martin Fowler [Fow1]

Duplicate Code

S'il y a bien une chose que nous permet Sass, c'est de pouvoir respecter le principle Don't Repeat Yourself (DRY).

Bien souvent, les duplications de code sont le témoignage de négligences, mais quand il s'agit de CSS, les duplications sont souvent inévitables.

La duplication la plus fréquente consiste à dupliquer la même valeur de propriété encore et encore. Un simple Extract Constant suffit à ne définir cette valeur qu'une seule fois.

Vient ensuite une duplication tout aussi fréquente mais moins apparente, la duplication de plusieurs propriétés CSS que l'on retrouve dans différents blocs. La solution consiste à appliquer soit Extract Declaration Block, soit Introduce Inheritance.

Enfin, les sélecteurs ne sont pas épargnés par toute cette duplication. Pour restreindre les déclarations CSS à une partie de la page, pas d'autre solution que de préfixer à chaque fois par le sélecteur du parent et ainsi de suite. Introduce Nesting supprime toute cette duplication et rend par la même occasion nos feuilles de style bien plus lisible et évolutive.

Long Block Declaration

L'impossibilité de factoriser des blocs de déclaration nous pousse soit à les dupliquer, soit à introduire une nouvelle classe CSS sans véritable valeur sémantique (ex : .rounded-box). Dans le premier cas, les blocs de déclaration s'allongent au risque de nuire à leur maintenabilité. Un simple Extract Declaration Block suffira à amincir considérablement vos blocs.

Large File

Plusieurs solutions existent pour diviser un fichier CSS. On peut inclure plusieurs feuilles à l'aide de la balise HTML link ou utiliser la règle CSS @import. Les deux obligent le navigateur à émettre une requête pour chaque feuille, ce qui augmente le temps de réponse, et aussi d'autres subtilités ([Sou1]).

Une solution courante est de concaténer les feuilles de style lors de la construction du projet (maven, grunt, ...). Appliquer Extract File est souvent plus simple.

Divergent Change

Difficile d'effectuer la moindre modification quand le code concerné est noyé par tout ce qui l'entoure. Il est préférable de structure le code de manière à ce que chaque changement nous conduise à un point bien précis dans le code.

Divergent Change se produit quand un même fichier a beaucoup de raisons de changer. En séparant par exemple la partie layout et les différentes parties du site, il devient facile de modifier le layout général, d'ajouter une nouvelle page ou d'en supprimer une existante.

En appliquant Extract File, chaque partie se retrouve dans sa propre feuille de style (ex : layout, homepage, contact, product).

Shotgun Surgery

Shotgun Surgery est en quelque sorte l'inverse de Divergent Change. Divergent Change se produit quand une feuille de style subit plusieurs types de changement alors que Shotgun Surgery se produit quand une modification implique plusieurs feuilles de style. Idéalement, chaque modification ne porte que sur une seule feuille de style.

Plusieurs refactorings peuvent s'appliquer. Extract Constant permet de condenser plusieurs valeurs vers une même déclaration. Inline File permet de regrouper deux feuilles de style très proches.

Comments

Les commentaires sont souvent nécessaires en CSS pour combler l'absence de variables, apportant avec elles la sémantique derrière une valeur magique. Paradoxalement, on hésite à inclure des commentaires car ceux-ci augmentent la taille des fichiers et se retrouvent inutilement dans le fichier téléchargé par le navigateur.

Extract Constant et Extract Declaration Block nous permettent de remplacer la plupart des commentaires par des noms tout aussi parlant, à la différence que ces noms font désormais partie intégrante du code.

Magic numbers

En parcourant des feuilles de style, on découvre souvent des nombres (ex : unités en px) qui sont identiques ou se ressemblent fortement. Le lien entre toutes ces valeurs n'est pas représenté explicitement. L'introduction de constantes (Extract Constant) est le premier pas pour limiter cela mais comment représenter que la taille est liée à tel autre élément. Introduce Arithmetic propose de représenter ce lien sous forme d'une expression mathématique où l'on viendra souvent réutiliser des constantes existantes.

Almost Identical

Almost Identical représente une situation où de nombreux selecteurs et blocs de déclaration se suivent, se ressemblent, mais ne sont pas identiques. L'utilisation de sprites CSS est l'exemple le plus parlant. Introduce Conditional Branching retire toute ressemblance et rend clairement apparent les quelques différences grâce à l'introduction des structures de contrôles (if, for, ...) incontournables dans les langages impératifs.

Extract Declaration Block peut également servir à factoriser le contenu des blocs de déclaration, notamment grâce à l'ajout de paramètres.

Introduce Inheritance doit être également considéré si les différents sélecteurs partagent une relation d'héritage avec une classe commune.

Color Explosion

La plupart des sites repose sur un nombre restreint de couleurs : une couleur principale et quelques couleurs secondaires. Ces couleurs sont souvent déclinées. Un bleu un peu plus clair par ici, un bleu un peu plus foncé par là. Cette déclinaison se traduit par la définition de nouvelles couleurs et avec cette explosion de couleurs, difficile de ne pas s'emmêler les pinceaux quand arrive le moment de changer nos quelques 2-3 couleurs de base.

En appliquant Extract Constant et Introduce Arithmetic, on va définit nos couleurs de base que l'on déclinera ensuite au besoin grâce à l'utilisation de fonctions permettant de mélanger des couleurs, les assombrir ou les éclaircir, etc.

Catalog of Refactorings

Le catalogue qui suit respecte le format de l'ouvrage référence ayant introduit le concept.

Extract Constant

La valeur d'une propriété se répète.

Remplacer la valeur par une variable initialisée à cette même valeur.

body {
 font: 100% Helvetica, sans-serif;
 color: #333;
}

=>

$font-stack:    Helvetica, sans-serif;
$primary-color: #333;

body {
  font: 100% $font-stack;
  color: $primary-color;
}
Motivations

Les motivations sont les mêmes que pour l'utilisation d'une constante dans un autre langage. Le code devient plus parlant grâce au nom judicieux de la constante. Mais surtout, la maintenance devient triviale en cas de changement de valeur.

Sass ne permet pas à proprement parlé la création de constantes mais uniquement de variables. Cela ne nous empêche pas d'exploiter les variables comme on le ferait pour déclarer une constante en Javascript.

Sass introduit également la notion de portée : une variable ne sera accessible qu'au sein du bloc où elle est déclarée (intéressant avec le Nesting). Une variable déclarée en dehors d'un bloc est considérée comme globale et pourra toujours être accédée, sauf si masquée par une autre déclaration.

$font: Arial, sans-serif;
$primary-color: #333;

article {
  $font: Helvetica, sans-serif;
  $secondary-color: #777;

  // Could use $font, $primary-color and $secondary-color
  // where $font = Helvetica, sans-serif
}

section {
  // Coud use $font and $primary-color
  // where $font = Arial, sans-serif
}
Mechanics
  • Créer une variable initialisée avec la valeur extraite
  • Remplacer les occurrences de cette valeur dans l'ensemble des feuilles de style
    • Vérifier à chaque remplacement que la variable est accessible. Le cas échéant, déplacer la variable au niveau du premier bloc parent commun. Ne jamais réduire la portée de la variable pour ne pas impacter les remplacements précédents.
  • Compiler et tester
Exemples
Exemple : (avec Nesting)
body {
  font-family: Helvetica, sans-serif;

  header {
    h1 {
      font-family: 'Cursive', sans-serif;
    }
  }

  footer {
    .footnote {
      font-family: 'Cursive', sans-serif
    }
  }

}

Cette feuille de style utilise deux polices différentes. Une par défaut (Helvetica) et une seconde manuscrite pour mettre en avant quelques textes comme les titres ou les notes.

On constate que la police 'Cursive', sans-serif est déclarée deux fois. On crée donc une variable avec cette valeur :

body {
  font-family: Helvetica, sans-serif;

  header {
    h1 {
      $font-manuscript: 'Cursive', sans-serif;
      font-family: 'Cursive', sans-serif;
    }
  }

  footer {
    .footnote {
      font-family: 'Cursive', sans-serif;
    }
  }

}

On se lance ensuite dans les remplacements.

body {
  font-family: Helvetica, sans-serif;

  header {
    h1 {
      $font-manuscript: 'Cursive', sans-serif;
      font-family: $font-manuscript;
    }
  }

  footer {
    .footnote {
      font-family: 'Cursive', sans-serif;
    }
  }

}

La variable créée se trouve hors de portée de la deuxième occurrence. Nous devons donc remonter la déclaration scope après scope jusqu'à trouver le premier qui satisfait les règles de visibilité.

body {
  $font-manuscript: 'Cursive', sans-serif;

  font-family: Helvetica, sans-serif;

  header {
    h1 {
      font-family: $font-manuscript;
    }
  }

  footer {
    .footnote {
      font-family: $font-manuscript;
    }
  }

}

La police Helvetica, sans-serif, bien qu'utilisée qu'une seule fois, gagnerait à être remplacée par une variable pour rendre explicite qu'il s'agit de la police par défaut mais aussi pour centraliser les choix de polices (Une modification est plus facile à faire si elle s'opère à un endroit bien défini). C'est ce que nous allons faire :

body {
  $font-default: Helvetica, sans-serif;
  $font-manuscript: 'Cursive', sans-serif;

  font-family: $font-default;

  header {
    h1 {
      font-family: $font-manuscript;
    }
  }

  footer {
    .footnote {
      font-family: $font-manuscript;
    }
  }

}

Extract Declaration Block

Plusieurs propriétés et leurs valeurs se répètent ou se ressemblent fortement.

Extraire ces déclarations dans un mixin dont le nom reflète l'intention du mixin.

.warning {
  border-radius: 10px;
  background-color: yellow;
  text-align: center;
}

.note {
  border-radius: 10px;
  background-color: silver;
  text-align: center;
}

.error {
  border-radius: 10px;
  background-color: red;
  text-align: center;
}

=>

@mixin information($background-color) {
  border-radius: 10px;
  background-color: $background-color;
  text-align: center;
}

.warning { @include information(yellow); }
.note    { @include information(silver); }
.error   { @include information(red);    }
}
Motivations

Les motivations sont identiques à la création d'une méthode dans un autre langage : factoriser le code pour n'avoir qu'un seul endroit à modifier (DRY). Cela permet également d'introduire de la sémantique à l'aide du nom choisi.

Les mixins sont le terme choisi par Sass pour représenter ce qui se rapproche de nos méthodes en Java. Ce terme, inspiré du langage Ruby. n'est pas surprenant puisque Sass a connu le succès à travers le framework Ruby on Rails.

Mechanics
  • Créer un nouveau mixin et choisir un nom reflétant l'intention ou le but du mixin.
    • S'il est difficile de trouver un nom, c'est peut-être un signe que le choix des déclarations extraites n'est pas pertinent. Une déclaration est peut-être en trop. Les déclarations serait mieux réparties en deux mixins, ...
  • Copier les déclarations retenues dans le mixin.
  • Identifier si les déclarations contiennent des références à des variables. Il peut être nécessaire, surtout pour maximiser la réusabilité du mixin, de déclarer ces variables comme paramètres.
  • Inspecter les déclarations en se demandant si une des valeurs mériterait d'être paramétrée (de manière à également maximiser la réusabilité du mixin). Créer un paramètre pour chaque valeur identifiée.
  • Remplacer les déclarations extraites par une inclusion au mixin. Les éventuels arguments doivent coïncider parfaitement avec les valeurs initiales.
  • Compiler et tester
Exemples
Exemple : Aucun paramètre
p {
  font-family: 'Cursive', sans-serif;
  line-height: 1.5em;
  font-weight: 400;
  color: white;
}

Les trois premières sont nécessaires pour obtenir une police manuscrite. Créons donc notre mixin :

@mixin manuscript {
  font-family: 'Cursive', sans-serif;
  line-height: 1.5em;
  font-weight: 400;
}
p {
  @include manuscript;

  color: white;
}
Exemple : CSS3 Polyfill
.box {
  -webkit-border-radius: 10px;
     -moz-border-radius: 10px;
      -ms-border-radius: 10px;
          border-radius: 10px;
}

.panel {
  -webkit-border-radius: 20px;
     -moz-border-radius: 20px;
      -ms-border-radius: 20px;
          border-radius: 20px;
}

Ce genre de code est devenu si habituel depuis l'arrivée du CSS3. Indispensable pour supporter les différentes versions des navigateurs, cela n'en reste pas moins regrettable.

@mixin border-radius($radius: 10px) {
  -webkit-border-radius: $radius;
     -moz-border-radius: $radius;
      -ms-border-radius: $radius;
          border-radius: $radius;
}

.box   { @include border-radius(); } /* Exploit default value */
.panel { @include border-radius(20px); }

Introduce Inheritance

Un élément possède deux classes sont l'un est plus spécifique que l'autre.

Étendre la classe spécifique de la classe générale.

<p class="error error-fatal">NullPointerException</p>
.error {
  color: red;
}
.fatal-error {
  font-weight: bold;
}

=>

<p class="error-fatal">NullPointerException</p>
.error {
  color: red;
}
.fatal-error {
  @extend error;
  font-weight: bold;
}
Motivations

Les classes CSS apportent de la sémantique à notre document (ex : footnote, warning, author, ...). Si on reprend l'exemple, la classe error nous signale une erreur. La classe error-fatal va même plus loin en nous indiquant que c'est une erreur fatale. D'un point de vue sémantique, seule cette dernière classe est nécessaire. L'ajout de la classe error est nécessaire en CSS pour factoriser les déclarations communes à tous les types d'erreur.

Sass propose un mécanisme d'héritage pour représenter les relations "est un" entre classes.

L'héritage ne doit pas être confondu avec les mixins, Tous deux permettent de factoriser une liste de déclarations. Toutefois, les mixins sont mieux adaptés pour de la présentation (ex : rounded-box, manuscript, ...) afin d'éviter l'ajout de classes sans sémantique dans notre document.

L'héritage apporte avec lui tout son lot de complexité, parfaitement décrite par le créateur du langage dans Sass and Compass in Action.

Mechanics
  • Identifier deux classes partageant une relation d'héritage au sein des pages HTML.
  • Etendre de la classe la plus générale dans chacune des définitions des classes plus spécifiques.
  • Supprimer la classe la plus générale uniquement dans les pages HTML et à condition qu'une des classes plus spécifiques soit présente sur cet même élément.
Exemples
Exemple :
.note {
  $note-size: 50px;

  display: block;
  width: $note-size;
  height: $note-size;
  background-image: url(notes-sprite.png);
  background-color: blue;
  border-radius: 50%;
}

.note-20 { background-position: 0 -50px;  }
.note-19 { background-position: 0 -100px; }
/* ... */
<span class="note note-20"></span>

Comme pour notre premier exemple, la classe note est redondante et n'apporte aucune valeur d'un point de vue sémantique. Rendons la relation d'héritage explicite grâce à Sass.

.note {
  /* idem */
}

.note-20 { @extend note; background-position: 0 -50px;  }
.note-19 { @extend note; background-position: 0 -100px; }
/* ... */
<span class="note-20"></span>

Extract File

La taille d'une feuille de style nuit à sa maintenabilité

Décomposer la feuille de style en plusieurs de manière à ce que chaque modification entraîne des changements dans une seule feuille de style.

/* style.css */
body {
  header  { /* ... */ }
  section { /* ... */ }
  aside   { /* ... */ }
}

.post {
  h1, h2 { /* ... */ }
  p      { /* ... */ }
  quote  { /* ... */ }
}

#contact {
  /* ... */
}

=>

// style.scss
@import "layout";
@import "post";
@import "content";

// _layout.scss
body {
  header  { /* ... */ }
  section { /* ... */ }
  aside   { /* ... */ }
}

// _post.scss
.post {
  h1, h2 { /* ... */ }
  p      { /* ... */ }
  quote  { /* ... */ }
}

// _content.scss
#contact {
  /* ... */
}
Motivations

Les feuilles de style commencent toujours petites mais ne le restent guère longtemps. Avec le temps, il devient de plus en plus difficile de localiser où les changements doivent être effectués.

De la même manière que l'on n'envisage pas de créer des classes Java de plusieurs milliers de lignes, pourquoi ne pas en faire autant avec nos feuilles de style ? D'autant plus que des solutions ont toujours existé (plusieurs balises link ou la règle @import).

Mais voilà, cette découpe est problématique pour les temps de réponse. Les deux solutions ne sont pas transparentes pour la navigateur qui a conscience de chacun des fichiers qui doivent être récupérés séparément.

Reste alors le recours à un outil de build (ex : grunt) pour concaténer l'ensemble des feuilles de style ou tout simplement utiliser Sass qui reprend la syntaxe du @import et veille à inclure les fichiers physiquement lors de la compilation vers CSS. (Sass appelle ces fichiers inclut des partials).

Mechanics
  • Créer un nouveau fichier en veillant bien à préfixer d'un underscore.
  • Déplacer l'ensemble des sélecteurs/blocs de déclaration sélectionnés dans ce fichier.
  • Ajouter la règle @import suivi du nom du fichier sans le underscore et sans extension.
Exemples
Exemple : Uniformisation

Voici la situation actuelle :

<!-- index.html -->
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="style.css">
// style.css
@import 'layout.css';

body {
  /* ...*/
}

// layout.css
header  { /* ... */ }
section { /* ... */ }
footer  { /* ... */ }

Sass nous permet d'uniformiser tout cela, tout en améliorant les temps de réponse de notre page.

<!-- index.html -->
<link rel="stylesheet" href="style.css"> <!-- Compiled stylesheet -->
// style.scss
@import 'reset';
@import 'layout';

body {
  /* ...*/
}

// _reset.scss
/* ... */

// _layout.scss
header  { /* ... */ }
section { /* ... */ }
footer  { /* ... */ }

Inline File

Des changements dans une feuille de style sont toujours accompagnés de changements dans une autre feuille de style.

Fusionner le contenu des deux feuilles de style.

// layout.css
@import 'header.css';
@import 'section.css';
@import 'footer.css';

// header.css
header {
  height: 200px;
  text-align: center;
}

// section.css
section {
  padding: 15px 25px
}

// footer.css
footer {
  height: 100px;
  text-align: center;
}

=>

// layout.css
header {
  height: 200px;
  text-align: center;
}

section {
  padding: 15px 25px
}

footer {
  height: 100px;
  text-align: center;
}
Motivations

Le fameux Divide and Conquer a ses limites. A trop diviser, on arrive à un découpage qui manque de cohésion. Si les modifications ne se résument jamais à une seule feuille de style, il y a fort à parier que le nombre de feuilles de style est trop important.

Réintégrer ces feuilles de style dans d'autres nous permet de revenir à davantage de cchésion. Sass n'est pas d'une grande utilité pour ce refactoring, présent par soucis de complétude avec les autres refactorings décrits.

Mechanics
  • Identifier la feuille de style la plus adaptée pour recopier le contenu de notre feuille de style.
  • Recopier l'intégralité du contenu en début de fichier si notre feuille de style était incluse avant ou en fin de fichier sinon.
  • Supprimer l'import de notre feuille de style avant de la supprimer physiquement.

Introduce Nesting

Les sélecteurs sont trop longs, trop répétitifs

Exploiter l'imbrication pour simplifier les sélecteurs et améliorer la lisibilité.

nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

nav ul li { display: inline-block; }

nav a {
  display: block;
  padding: 6px 12px;
  text-decoration: none;
}

=>

nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  li { display: inline-block; }

  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}
Motivation

La syntaxe CSS traite chaque sélecteur indépendamment. Malheureusement, en pratique, les sélecteurs sont souvent liés entre eux mais impossible de faire ressortir ces liens.

Sass avec sa syntaxe Nesting nous épargne beaucoup de répétitions et facilite grandement la lecture. Cette fonctionnalité mérite à elle seule l'utilisation de Sass.

Attention toutefois à ne pas reproduire à l'identique la structure HTML en CSS. N'oublions que Sass doit compiler nos feuilles de style vers du CSS et que des sélecteurs trop précis peuvent dégrader les performances.

Mechanics
  • Identifier un sélecteur ayant comme préfixe un autre sélecteur.
  • Recopier le sélecteur (sans le préfixe) et l'ensemble de son bloc de déclarations à la fin du bloc de déclaration du sélecteur parent.
  • Supprimer la déclaration avec l'ancien sélecteur.
Exemples
Exemple : Décomposition par page
#posts            { padding: 15px; }
#posts article h1 { font-weight: bold; }
#posts article p  { margin-bottom: 2em; }
#posts aside      { background-color: silver; }

L'utilisation du nesting améliore la clarté des feuilles de style. (Discutable sur cet exemple très simple)

#posts {
  padding: 15px;


  article {

    h1 {
      font-weight: bold;
    }

    p {
      margin-bottom: 2em;
    }

  }


  aside {
    background-color: silver;
  }

}
Contre exemple : Attention au CSS généré
#post, #index, #contact {
  header, footer {
    p {
      margin-top: 20px;
      margin-bottom: 20px;
    }
  }
}

Compilé en CSS, cela devient moins séduisant, surtout pour inspecter avec Firebug ou autre...

#post header p, #post footer p, #index header p, #index footer p, #contact header p, #contact footer p {
  margin-top: 20px;
  margin-bottom: 20px;
}

Introduce Arithmetic

Un nombre magique ne parait pas si magique.

Remplacer ce nombre par une expression mathématique expliquant sa valeur.

#main{
  width: 500px;
  padding: 10px;

  .cell {
    width: 25px;
    padding: 5px;
  }
}

=>

$grid-cells: 20;
$cell-width: 25px;

#main{
  $main-width: $grid-cells * $cell-width;
  $main-padding: 10px;

  width: $main-width;
  padding: $main-padding;

  .cell {
    width: $cell-width;
    padding: $main-padding / 2;
  }
}
Motivations

Les nombres magiques sont omniprésents en CSS. Voir le lien entre deux nombres est loin d'être évident, surtout lorsque c'est deux nombres sont présents dans deux feuilles de style différentes. Bien souvent, on change un des nombres est le résultat n'est pas celui espéré. S'en suit alors une série de modifications qui visent à recalculer les nombres liés.

Le CSS nous aide déjà grâce à l'utilisation de mesures relatives (pourcentage, em). Ex :

.circle {
  width: 100px;
  height: 100px;
  border: 1px solid red;
  border-radius: 50%; /* Changes on the diameter are automatically reflected */
}

SASS va plus loin. Grâce à l'utilisation d'expressions, les calculs deviennent apparents. Grâce à l'utilisation de variables (voir Extract Constant), ils deviennent encore plus compréhensibles. Leur modification devient alors aisée.

Cela ne remet pas en cause l'utilisation d'unités relatives qui doit être privilégiées lorsque cela est applicable.

Sass supporte plusieurs types (booléen, string, nombre, couleur, liste) et propose même des fonctions.

Mechanics
  • Créer une variable (Extract Constant) contenant la valeur dont d'autres valeurs dépendent.
  • Pour chaque variable dépendante
    • Vérifier à chaque remplacement que la variable est accessible. Le cas échéant, déplacer la variable au niveau du premier bloc parent commun. Ne jamais réduire la portée de la variable pour ne pas impacter les remplacements précédents.
    • Remplacer l'ancienne valeur par une expression utilisant la variable précédemment créée.
      • Attention à la priorité des opérateurs. Utiliser des parenthèses pour changer l'ordre d'évaluation ou pour clarifier l'expression.
  • Compiler et tester
Exemples
Exemple : Coloration tableau

See the Pen wBMmVP by Julien Sobczak (@julien-sobczak) on CodePen.

L'arrivée du sélecteur nth-child nous épargne l'ajout de nombreuses classes CSS sans valeur sémantique :

tr {

  &:nth-child(2n) {
    td {
      background-color: #EEE;

      &:nth-child(2n) {
        background-color: #CCC;
      }
    }
  }

  &:nth-child(2n+1) {
    td {
      background-color: #AAA;

      &:nth-child(2n) {
        background-color: #888;
      }
    }
  }
}

4 couleurs sont nécessaires pour parvenir au résultat escompté. Deux d'entre elles correspondent aux couleurs principales (celles à alterner). Les deux autres sont étroitement liées puisqu'elles représentent ces mêmes couleurs avec de légères variations pour représenter le croisement des lignes et colonnes.

Modifier les couleurs de base nous obligent à recalculer les couleurs secondaires.

Comparons avec cette autre solution, reposant sur les expressions SASS permettant de créer de nouvelles couleurs à partir d'existantes :

$odd-row-color: silver;
$even-row-color: gray;

tr {

  &:nth-child(2n) {
    td {
      background-color: $odd-row-color;

      &:nth-child(2n) {
        background-color: darken($odd-row-color, 20%);
      }
    }
  }

  &:nth-child(2n+1) {
    td {
      background-color: $even-row-color;

      &:nth-child(2n) {
        background-color: darken($even-row-color, 20%);
      }
    }
  }
}

La relation entre les couleurs devient de suite plus parlante et tenter différentes couleurs devient alors un vrai plaisir.

Introduce Conditional Branching

Beaucoup de duplications entre une suite de sélecteurs/blocs de déclarations.

Utiliser les structures de contrôles habituelles pour factoriser le code commun.

.roti {
  background-images: url(sprites.png);
  width: 50px;
  height: 50px;
}
.roti1 { background-position: 0      0; }
.roti2 { background-position: 0  -50px; }
.roti3 { background-position: 0 -100px; }
.roti4 { background-position: 0 -150px; }
.roti5 { background-position: 0 -200px; }

=>

.roti {
  background-images: url(sprites.png);
  width: 50px;
  height: 50px;
}
@for $i from 1 through 5 {
  .roti#{$i} { background-position: 0 ($i - 1) * -50px; }
}
Motivations

Le CSS a beau être un langage déclaratif, il n'en reste pas moins que nos bonnes vieilles instructions de contrôle héritées des langages impératifs se font parfois sentir absentes.

Même si les cas d'utilisation restent marginaux, nous verrons deux exemples où les structures de contrôle se révèlent indispensables.

Exemple
Exemple 1 : Les sprites

Les sprites sont présentes dès la première règle du classique de Steve Souders, High Performance Web Sites. Leur impact sur les temps de réponse est conséquent. Là, où des dizaines de requêtes HTTP étaient nécessaires auparavant, une seule suffit grâce à l'utilisation de sprites.

Côté CSS, cela est rendu possible grâce à la propriété background-position.

Prenons pour exemple le sprite suivant :

Voici un exemple de code CSS nécessaire pour exploiter les différentes icônes.

ul {
    list-style-type: none;
}

li {
  display: inline-block;
  width:  100px;
  height: 100px;
  background-image: url(categories-sprite.png);
}
li.category-read    { background-position: 0      0; }
li.category-watch   { background-position: 0 -100px; }
li.category-tell    { background-position: 0 -300px; }
li.category-inspect { background-position: 0 -400px; }
// ...

Pour cet exemple, nous allons définir une variable pour contenir la liste de nos catégories, avant d'itérer dessus pour générer l'ensemble des déclarations. Voici le résultat :

ul {
  $categories: read, watch, tell, inspect;

  list-style-type: none;


  li {
    display: inline-block;
    width: 70px;
    height: 60px;
    background-image: url(labels-sprite.png);
  }

  $i: 0;
  @each $category in $categories {
    li.category-#{$category} { background-position: 0 (-100px * $i); }
    $i: $i + 1;
  }

}

Grâce à Sass et l'utilisation de structures de contrôle, notre application devient à la fois plus performante et plus maintenable. L'ajout d'un nouveau libellé revient simplement à modifier notre variable.

Références

[Fow1]
Refactoring: Improving the Design of Existing Code
[Sou1]
don’t use @import

A voir également