Si vous êtes allez sur mon blog récemment, vous avez surement du remarquer quelques modifications esthétiques ? Non ? Vraiment… ? :(

Bon ok, je vous aide : un thème sombre !

Pourquoi ?

Cela fait quelque temps que ce sujet était dans mes cartons, et ce, pour plusieurs raisons.

La première était de prendre un peu plus la main sur le thème que j’avais forké au moment de la création du blog (Tranquilpeak par Thibaud Lepretre). J’y ai déjà fait plusieurs modifications mineures, mais je souhaitais pouvoir creuser plus profondément. A ce sujet, il va surement y avoir d’autres futures grosses évolutions (plus ou moins visibles), comme la mise à jour des dépendances par exemple.

La seconde était la découverte d’une issue sur le GitHub du projet original demandant la mise en place d’un thème sombre (https://github.com/kakawait/hugo-tranquilpeak-theme/issues/384). L’auteur trouvait l’idée bonne, mais ce n’était pas dans ses priorités. J’espère pouvoir faire une PR de ma feature dès que celle-ci sera totalement stabilisée et propre.

Enfin la dernière est que je suis plutôt fan du thème sombre sur l’ensemble des outils que j’utilise (applications mobiles, site web, logiciels…). J’utilise d’ailleurs une excellente extension sur Firefox qui permet d’assombrir automatiquement les sites web : Dark Reader. Je vous la conseille ;)

Avantages et inconvénients

Le thème sombre est aussi un sujet à la mode depuis quelques années déjà et il se démocratise de plus en plus, que ce soit sur le web ou sur les stores mobiles. De nombreux services ont déjà cédé aux sirènes des ténèbres : Facebook (via Messenger pour le moment), Instagram, Netflix, YouTube, Slack, Twitter. La plupart des systèmes d’exploitation ont aussi sauté le pas : Apple dans iOS 12 et MacOS, Google à partir d’Android 9 et même Microsoft dans Windows 10. La population des développeurs avait déjà l’habitude de ce style graphique, qui est disponible dans de nombreux éditeurs de code depuis pas mal de temps.

L’objectif initial est de réduire l’impact sur notre santé ainsi que notre consommation d’énergie.

Concernant la santé, il s’agit essentiellement de limiter la fatigue oculaire, l’éblouissement et l’exposition à la lumière bleue (qui est naturellement moins présente dans les couleurs sombres) qui peut causer des troubles du sommeil. Avec de nombreuses heures passées sur les écrans, nos yeux sont mis à rude épreuve, en particulier en situation de faible luminosité (matin et soir, travail nocturne…) et à défaut de réduire le temps passé sur les écrans (ce qui est la meilleure solution ;)), autant se mettre dans les meilleures conditions !

Concernant l’environnement, l’écran est généralement l’élément le plus consommateur d’énergie dans nos usages numériques (c’est particulièrement vrai sur smartphone et avec un écran OLED). Avec cette technologie d’écran, un pixel affichant du noir est un pixel désactivé et ne consommant donc pas d’énergie. Mais cela est valable aussi sur des écrans LED “classiques”, Google a donné une explication à ce sujet lors du Android Dev Summit de 2018. Réduire sa consommation est donc intéressant pour l’autonomie de nos appareils, mais aussi pour contribuer à protéger la planète.

Comment ?

De prime abord, on pourrait se dire “remplaçons le fond noir par un fond blanc et passons les textes noirs en blanc et l’affaire sera réglée”. Évidemment, pour obtenir un résultat satisfaisant, c’est un peu plus compliqué que cela… ;)

Il existe plusieurs règles (ou plutôt convention) d’UI/UX pour la mise en place d’un thème sombre. Google propose un guide d’excellente qualité sur son site dédié à Material Design et Apple a un équivalent pour iOS et MacOS.

Voilà un récapitulatif des conseils que j’ai trouvé les plus utiles :

  • On évite l’utilisation du noir “pur” (#000000) pour ne pas avoir un contraste trop important par rapport au texte et on privilégie un gris très foncé. Google conseille l’utilisation de #121212. Cela permet de jouer avec les différentes nuances de gris (sans mauvais jeu de mots…). On peut aussi décider de partir de la couleur dominante de son site et de la foncer fortement.
    Par exemple, en prenant comme base la couleur **#45E2A1**, on va lui appliquer une opacité de 12% sur un fond noir, ce qui va nous donner le résultat suivant (je vous conseille d'ouvrir l'image dans un nouvel onglet pour bien voir les couleurs) Par exemple, en prenant comme base la couleur **#45E2A1**, on va lui appliquer une opacité de 12% sur un fond noir, ce qui va nous donner le résultat suivant (je vous conseille d'ouvrir l'image dans un nouvel onglet pour bien voir les couleurs)
  • Contrairement à la version claire, il est conseillé d’éviter l’utilisation de grand aplat de couleur qui vont avoir tendance à “agresser” les yeux de l’utilisateur.
  • On va essayer de réduire la luminosité des couleurs et, si nécessaire, augmenter leur contraste (sur les boutons par exemple).
  • De la même façon, on va réduire la luminosité des images. A ce sujet, il est parfois nécessaire d’en revoir certaines, notamment dans les cas de fond transparent.
  • Un texte clair sur fond sombre apparait plus épais qu’un texte sombre sur fond clair. Il est donc conseillé de réduire légèrement la graisse des textes (400 -> 350). De même, il est conseillé d’appliquer une légère opacité sur les blancs utilisés pour les textes.
  • Dans un thème sombre, il est compliqué d’utiliser les ombres pour gérer la hiérarchie des éléments. On va donc travailler sur l’élévation (axe z), ce qui se traduit par jouer sur l’illumination, généralement en appliquant un calque blanc et en faisant varier son opacité/sa transparence. Plus l’élément est proche de l’utilisateur, plus il sera clair.

Et techniquement, ça donne quoi ?

Après avoir balayé l’aspect théorique des choses, il est temps de rentrer “dans le dur” et de parler des modifications techniques que j’ai appliquées sur le thème.

prefers-color-scheme

De la même façon que pour les feuilles de style dédiées à l’impression (@print), il existe une media query permettant de détecter si l’utilisateur a activé le thème sombre dans son système d’exploitation ou son navigateur :

@media (prefers-color-scheme: dark) {
    body {
        color: #fff;
        background: #121212;
    }
}

C’est très pratique pour venir surcharger les règles existantes pour les adapter au thème sombre, mais aussi pour ajouter des règles supplémentaires.

Si le navigateur ne supporte pas cette media query (cf. Caniuse), alors elle sera tout simplement ignorée.

SCSS et mixins

SCSS est un préprocesseur CSS permettant de lui apporter plus de puissance et de confort. Il intervient donc avant la feuille de style et sera compilé pour obtenir un fichier CSS valide.

Il apporte de nombreux avantages, aussi bien au niveau maintenabilité et lisibilité que fonctionnement. Voici les principaux :

  • L’imbrication : évite de répéter les sélecteurs HTML et rend la structure plus logique et lisible par rapport au DOM.
  • Les variables : permet de stocker une valeur pour ensuite l’utiliser à plusieurs endroits. C’est extrémement pratique pour modifier une couleur ou une taille sur l’ensemble d’un site.
  • Les conditions : permet de définir un style en fonction d’une variable.
  • Les boucles : permet, notamment, la génération rapide de code similaire.
  • Les mixins : probablement “the killer feature” :D Cela permet de créer des blocs de code réutilisables à d’autres endroits (un effet de transition par exemple) via la directive @include.

Pour mon cas, j’ai créé une mixin themed me permettant de décliner un style pour chaque thème. Actuellement, j’en ai 2 (clair et sombre), mais si à l’avenir je souhaite en ajouter d’autres, ce système le permet simplement.

$themes: (
    light: (
        main-bg: #fff,
        header-bg: #fff,
        font-color-base: #5d686f
        ...
    ),
    dark: (
        main-bg: #121212,
        header-bg: #181A1B,
        font-color-base: #e8e6e3
        ...
    )
);

@mixin themed() {
  @each $theme, $map in $themes {    
    .theme--#{$theme} & {
      $theme-map: () !global;
      @each $key, $submap in $map {
        $value: map-get(map-get($themes, $theme), '#{$key}');
        $theme-map: map-merge($theme-map, ($key: $value)) !global;
      }
      @content;
      $theme-map: null !global;
    }
  }
}

@function t($key) {
  @return map-get($theme-map, $key);
}

Concernant son utilisation, c’est très simple et cela permet d’identifier rapidement quelles sont les propriétés concernées par le thème.

#main {
    display: block;
    min-width: $main-width;
    ...
    @include themed() {
        background: t('main-bg');
    }
}

Et enfin, une fois compilé, voilà le résultat en CSS :

.theme--light #main {
    background: #fff; 
 }
 .theme--dark #main {
    background: #121212; 
 }

Gestion de la luminosité

Comme vu plus haut, en mode sombre, il est conseillé de réduire la luminosité des images et d’augmenter légèrement leur contraste. Pour cela, j’utilise la propriété CSS image-filter (que j’applique via ma mixin sur les balises img des articles) :

image-filter: brightness(.8) contrast(1.2)

Concernant les couleurs, je vais aussi jouer sur la luminosité en éclaircissement ou assombrissant en fonction des cas. Pour cela, je passe par les fonctions lighten et darken :

lighten('red', 45%)
darken('red', 35%)

Détection via Javascript

J’ai choisi de stocker le thème de l’utilisateur dans le LocalStorage (pour se souvenir de son choix d’une visite à l’autre). Mais il est tout de même nécessaire de déterminer une valeur la 1ère fois.

Voilà le flow permettant de prendre la décision :

graph TD A[How to determine a theme ?] --> B{Is there a value in localStorage ?} B -->|Yes| C(Apply the found theme); B -->|No| D{Look at the preferences of
the OS / browser
via prefers-color-scheme} D -->|light| G(Apply light theme); D -->|dark| H(Apply dark theme); D -->|no-choice| H(Apply dark theme); style A stroke-width:3px style C fill:#004A7F,stroke:#333,stroke-width:2px,color:#fff style G fill:#fff,stroke:#333,stroke-width:2px,color:#000 style H fill:#121212,stroke:#333,stroke-width:2px,color:#fff

Vous noterez que par défaut, j’ai choisi que le site soit en mode sombre ;)

Pour faire cela, il faut interroger les medias queries via Javascript :

if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    // dark mode 
} else {
    // light mode 
}

On peut aussi s’abonner à l’évènement notifiant du changement de thème par l’utilisateur pour que notre site y réagisse et applique directement le bon thème (à ce propos, Chrome permet, via les outils de développement, de simuler la modification de ce réglage) :

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
    const newColorScheme = e.matches ? "dark" : "light";
    setTheme(newColorScheme);
});

Toggle

Parce qu’il est important d’avoir le choix, j’ai ajouté un toggle qui permet de basculer entre les thèmes clair et sombre. Pour cela, je me suis très largement inspiré du composant de cet article, sur lequel j’ai simplement joué sur les tailles et les couleurs. Merci à l’auteur ;)

Ce toggle est présent dans la barre latérale et accessible aussi bien en mode desktop que mobile.

Pour conclure, voici quelques liens d’outils en ligne qui m’ont bien aidé :

J’espère que le nouveau “style” du blog vous plait (si ce n’est pas le cas, l’ancien est toujours disponible via le toggle ;) ). N’hésitez pas à me remonter les éventuels soucis que vous pourriez rencontrer.