Précisions sur les sélecteurs et bonnes pratiques CSS

Alexandre Niveau
GREYC — Université de Caen
  • TODO: pas mal de choses à revoir, les démos ne sont pas toujours claires
  • ajouter slides sur la factorisation : des règles entre plusieurs fichiers CSS, des règles entre elles

Rappels

On a vu trois sortes de sélecteurs
  • Sélectionner les élements d'un certain type : le sélecteur est simplement le nom du type. Exemples : nav, em
  • Sélectionner les éléments d'une certaine classe : le sélecteur est un point (.) suivi du nom de la classe. Exemples : .info-complementaire, .photo
  • Sélectionner les éléments d'un certain identifiant : le sélecteur est un « croisillon » (#) suivi du nom de l'identifiant. Exemples : #pied-de-page, #item-actif
On a aussi vu deux façons d'associer des sélecteurs
  • Avec des virgules, on peut appliquer une règle aux éléments qui satisfont un sélecteur parmi plusieurs (disjonction). Exemple : h1,p,.toto sélectionne les éléments de type h1, les éléments de type p, et les éléments de classe toto.
  • En séparant deux sélecteurs par une espace (combinaison), on met une contrainte sur l'élément sélectionné ainsi que sur ses parents. Exemple : .toto p sélectionne les paragraphes dont au moins un parent est de classe toto.

Conjonction de sélecteurs

Une façon de combiner les sélecteurs que l'on n'a pas encore vue est la conjonction (qui correspond à l'opérateur logique « et »)

Permet de sélectionner des éléments qui doivent satisfaire plusieurs sélecteurs à la fois

Pour obtenir cette combinaison, il suffit de regrouper les sélecteurs ensemble, sans espaces

Ex: img#toto.intro sélectionne les éléments de type img ayant l'identifiant toto et la classe intro.

NB: on ne peut pas faire la conjonction de sélecteurs d'éléments, mais c'est pas grave

Association de différents types de sélecteurs

  • Il peut être utile d'associer un sélecteur de classe avec un sélecteur de type d'élément
  • Exemple : j'ai une page sur des opérations mathématiques. Je veux mettre en valeur les noms des opérations dans le texte. Chaque opération a également un encadré avec plus d'informations. Je peux utiliser le CSS suivant :
    span.operation {
        font-weight: bold;
    }
    
    aside.operation {
        border: 2px solid black;
        float: right;
    }
    
  • Intérêt : pas besoin d'une classe différente pour les deux utilisations, on s'appuie sur la structure du document.
  • En pratique, c'est rarement utile et c'est même une assez mauvaise idée, pour des raisons de maintenance :
    1. c'est plus facile de s'y retrouver dans son CSS si les noms de classes ne sont utilisés que pour un seul usage précis
    2. il faut éviter de rendre le CSS trop dépendant du HTML : si on décide de remplacer le aside par un autre élément, on casse tout alors que les classes ne servent que pour le CSS, pas de raison de les changer

Classes multiples

  • On peut mettre plusieurs classes sur un élément HTML. Syntaxe :
    <div class="foo bar">je suis de classe foo et de classe bar!</div>
    ⇒ on sépare les classes par des espaces dans l'attribut class
  • Avec le CSS suivant
    .foo { color: yellow; }
    .bar { border: solid 4px red; }
    
    l'élément sera en jaune avec une bordure rouge.
  • C'est une bonne chose : on peut mutualiser davantage de code. Exemple d'utilisation Trouver un meilleur exemple, qui montre davantage la notion d'orthogonalité + ne donne pas d'exemple de classes co-dépendantes…
  • Note : rien n'empêche de sélectionner les éléments qui ont plusieurs classes à la fois
     .foo.bar { border-style: dashed; }
    Peut être utile, mais pas évident à maintenir : en général on essaie plutôt d'éviter que les classes dépendent les unes des autres.

Factorisation du code

Comme toujours en informatique, on cherche à factoriser au maximum son code CSS, c'est-à-dire à éviter les répétitions (principe DRY).

Objectif visé : principalement la facilité de maintenance

Voir le tutoriel sur la factorisation CSS de Jean-Marc Lecarpentier

TODO: voir ce que je fais de ça. c'est pas entièrement pertinent.

Combiner id et class

  • Utiliser id pour les propriétés uniques d'un élément
  • Utiliser class pour des propriétés partagées (ou s'il y a une sémantique derrière la présentation)
Exemple : montrer du code
  • Page avec 2 menus (d'identifiants #menu1 et #menu2), de style identique, sauf que l'un est en bleu et l'autre en rouge.
  • On leur met une classe menu, pour ce qui est commun, et on utilise les identifiants pour changer simplement les couleurs
  • D'autre part, on utilise .menu a pour styler les liens des menus
    ⇒ pas besoin de nouvelle classe lienMenu
    ⇒ inutile de dupliquer les propriétés avec 2 sélecteurs #leftMenu a et #rightMenu a
à caser :

* Identifiants : ils ne sont pas interdits, mais leur utilisation doit être justifiée. Normalement ils doivent servir pour modifier le style d'un élément précis, sans qu'il y ait de catégorie/sémantique associée. Typiquement, pour styler le menu principal, une classe est a priori plus appropriée (même s'il n'y a bien sûr qu'un seul menu principal sur la page). Mais ici encore c'est très subjectif (encore davantage que l'équilibre structure/classes). Les correcteurs/trices sont libres d'interdire les identifiants ou au contraire de les autoriser un peu plus largement.

Spécificité des sélecteurs

  • Plusieurs sélecteurs peuvent donner des valeurs différentes à une même propriété
  • Règle de base en CSS : c'est le dernier qui gagne
  • En réalité, ce n'est pas toujours le cas : c'est le plus spécifique qui gagne
  • On calcule la spécificité d'un sélecteur comme indiqué ici (voir aussi cet article plus visuel sur CSS-Tricks et cet article qui explique le calcul avec des personnages de Star Wars).
  • Une astuce : en utilisant le sélecteur [id="toto"] plutôt que #toto, on vise le même élément mais avec une spécificité moindre
  • Plus d'explications dans cette démo : Illustration des effets inattendus de la spécificité exemple plus concret et plus court ?
  • Au passage, on peut forcer une propriété à s'appliquer avec !important. Ne pas le faire dans du vrai code : ça complique (encore plus) le débogage et la maintenance. Ça peut être utile pour tester ou déboguer (ou dans les cas où on doit ajouter des styles par-dessus du CSS qu'on ne contrôle pas).
* :where et :is
* CSS layers ! super explications sur la cascade dans cet article

Héritage

Dans l'exemple suivant, les deux propriétés CSS se comportent de manière différente

<article>
    <h2>Titre de l'article</h2>
    <p>Un paragraphe: mon article est <em>vraiment</em> super.</p>
    Ce texte n'est pas dans un paragraphe.
</article>
article {
    font-style: italic;
    border: 3px dotted red;
}
Seule la boîte de l'article a une bordure, mais alors pourquoi le h2 est en italique ?

Contrairement à border, font-style est une propriété héritable

Chaque élément hérite de toutes les propriétés héritables de ses ancêtres

Le font-style du h2 est donc hérité de celui de l'article (c'est-à-dire qu'il prend la valeur italic)

En revanche le border du h2 est celui par défaut (c'est-à-dire pas de cadre)

Héritage jusqu'aux feuilles de l'arbre généré par le DOM

Si on veut que les em ne soient pas en italique, on est obligé de le dire explicitement

(j'enlève car les propriétés plus récentes initial, unset et surtout revert sont plus adaptées de toute façon) -- Inversement, on peut forcer une propriété à être héritée avec la valeur spéciale inherit (très utile comme « valeur par défaut » pour les propriétés héritables)

En général on devine intuitivement quelles propriétés sont héritables ou non (regarder une réf en cas de doute)

Annuler une propriété CSS

Parfois on a spécifié une propriété CSS pour un cas général, et on a besoin de l'« annuler » pour un cas particulier

Cela arrive notamment avec les media queries

On peut utiliser les valeurs spéciales initial, inherit, unset, et surtout revert

  • initial revient à la valeur « par défaut » de la propriété : par exemple pour color, c'est black
  • inherit force à hériter de la valeur du parent : typiquement plus approprié justement pour color, mais pas pour d'autres
  • unset est plus pratique : revient à faire inherit pour les propriétés héritables (comme color), et initial pour les autres (comme background).
  • revert fonctionne comme unset, sauf qu'il ne remet pas les propriétés complètement à zéro, mais revient à la valeur qu'elles ont dans la feuille de style du navigateur. ⇒ on annule vraiment la modification, sans effet de bord. exemple: display:unset vaudra toujours `inline`, car c'est la valeur initiale de display. C'est indépendant de l'élément.

De façon générale, il vaut mieux toujours utiliser revert, sauf si vous savez vraiment ce que vous faites ou si le résultat ne vous convient pas…

le shorthand all est aussi utilisable partout (pas vraiment utile d'en parler en L1) spec

Éviter les sélecteurs trop génériques

De façon générale, on essaie d'éviter que le CSS dépende trop de la structure du HTML

Notamment, il faut faire attention aux sélecteurs trop génériques

Vous utilisez souvent directement les éléments suivants comme sélecteurs, parce qu'il n'y a pas d'ambiguïté dans vos pages :

header { /* ... */ }
footer { /* ... */ }
nav { /* ... */ }
article { /* ... */ }
section { /* ... */ }
aside { /* ... */ }
Cependant, ce ne sont pas de bons sélecteurs, car :
  • il peut y avoir plusieurs header, footer, nav, etc. dans une page HTML. Même si ce n'est pas le cas de votre page pour l'instant, il faut prévoir qu'elle puisse évoluer.
  • le contenu de votre page peut évoluer et nécessiter de changer vos choix d'éléments sémantiques de structuration (par exemple, changer le aside en article voire en div)
Ces sélecteurs ne sont donc pas très robustes, il vaut mieux (généralement) utiliser des classes.

Longueur des sélecteurs

  • Il ne faut pas utiliser des sélecteurs trop « longs », qui suivent de trop près la structure du HTML
  • Exemple : #entete .menu ul li a
  • il suffit a priori de mettre .menu a (on sélectionne les liens du menu). S'il y a plusieurs sortes de liens dans le menu, alors il vaut mieux leur donner des classes distinctes.
  • Les sélecteurs trop longs posent encore une fois des problèmes de maintenabilité
    1. le CSS est très dépendant de la structure du HTML : si on change le HTML, on risque de casser le CSS c'est une autre catégorie de sélecteur pas très robuste
    2. plus les sélecteurs sont longs, plus leur spécificité est grande : leurs effets sont donc plus difficiles à « annuler » si besoin
  • Il n'y a pas de règle absolue, mais trois « étages » pour un sélecteur est a priori un maximum. S'il y a besoin de plus, se poser la question de l'ajout d'une classe.

Ne pas s'appuyer que sur les classes

Une stratégie pour éviter ces problèmes peut être de n'utiliser que des classes, c'est-à-dire de créer une classe à chaque fois qu'on a besoin de styler un élément, et de n'avoir que des sélecteurs avec une seule classe.

On évite effectivement la dépendance à la structure du HTML, mais cela devient vite très lourd à maintenir, et le CSS est moins facile à lire puisqu'il donne peu d'indications sur la structuration de la page et les éléments à styler. Exemple :

  .lien-bibliographie {
      color: pink;
      text-decoration: none;
  }

pourquoi préciser ici text-decoration: none ? Il serait probablement plus approprié de mettre une classe au bloc englobant et d'utiliser celle-ci dans le CSS :

  .bibliographie a {
      color: pink;
      text-decoration: none;
  }

Ici il est plus clair que l'on enlève le soulignement par défaut de l'élément a.

Il n'est pas toujours évident de trouver un bon équilibre, et les critères peuvent être assez subjectifs ! Mais dans tous les cas il faut éviter les deux extrêmes (aucune classe / que des classes).

Structurer ses fichiers CSS

  • Créer des fichiers CSS séparés selon des catégories
  • Le but est d'utiliser une même feuille de style sur un maximum de pages
  • Ordre des déclarations : plusieurs choix sont possibles, à condition de rester cohérent et facile à comprendre. Exemple : en largeur d'abord, càd d'abord la mise en page des gros blocs, puis les détails de chaque bloc, et ce récursivement.
  • Dans tous les cas, commenter le code pour faciliter la maintenance !
    Le minimum absolu est de séparer les règles en groupes logiques avec des sortes de catégories ou sections :
    /**** BANNIÈRE ****/
    
    .banniere {
      background-color: black;
    }
    
    .logo {
      color: white;
      background-color: blue;
    }
    
    /**** MENU ****/
    
    .menu {
      border-radius: 1em;
      display: grid;
      grid-template-columns: 15em 15em 15em;
    }
    
    .menu a {
      border: 1px solid red;
    }
    
    /**** PRODUITS ****/
    
    .produit {
      font-style: italic;
    }
    
    .produit h2 {
      font-family: YoupiSans;
    }
    
    .price-tag {
      color: red;
    }
    
  • Éviter les CSS très longues : essayer de diviser en modules indépendants.

Utiliser plusieurs fichiers CSS

  • Si on a bien divisé son CSS en modules, on a besoin d'utiliser plusieurs fichiers CSS pour chaque page HTML.
  • Pour ce faire, il suffit de mettre plusieurs <link rel="stylesheet"> dans le head
  • Une autre possibilité offerte par CSS : la règle @import
    • @import url("mystyle.css"); pour importer une CSS
    • Permet d'avoir une seule feuille de style pilote qui appelle toutes les CSS du site, divisées en modules
    • Attention : les imports doivent être faits avant toute autre déclaration dans les CSS !
    Mais il est très déconseillé d'utiliser @import : empêche le téléchargement des CSS en parallèle, ce qui bloque le rendu
  • Ce problème n'arrive pas si on met plusieurs <link rel="stylesheet"> dans le HTML : les fichiers peuvent être chargés en parallèle.