Utiliser les variables CSS pour changer un style de page sans rechargement

Logo

Introduction

Lors de la conception d’un site, éviter les rechargements des pages pour mettre à jour des données ou des styles est souvent un prérequis.

Les variables CSS sont très utiles pour changer un style de page sans recharger la page.

Dans cet article, le bouton bascule (toggle) dans l’en-tête des pages qui permet de passer du style "jour" au style "nuit" est expliqué : principalement des variables CSS, une variable cookie et très peu de code Javascript.

Le bouton bascule (toggle)

Le bouton bascule est une case à cocher, rien de plus. Comment concevoir un bouton bascule n’est pas l’objectif ici, cet excellent article décrit très bien ce sujet : Dev.to, Danielpdev - Build a toggle button with only HTML and CSS

                       <div class="toggle-container">
              <input type="checkbox">
              <div class="slider round"></div>
            </div>
          

Les variables CSS

Autant que possible, dans la feuille de style principale, les couleurs d'arrière-plan ou de premier plan des éléments ne sont pas codées en dur, des variables CSS sont utilisées (seules les propriétés pertinentes sont affichées ci-dessous par souci de concision):

style.css
body {
  color: var(--wrap-fg-color);
  background-color: var(--page-bg-color);
}

div[id="wrap"] {
  background-color: var(--wrap-bg-color);
}

h1, h2, h3, h4, h5 {
  font-family: var(--heading-font-family);
  color: var(--heading-fg-color);
}

a { color: var(--anchor-color); }

section { border-color: rgb(var(--box-bg-color)); }

section h2 {
    background-color: rgb(var(--box-bg-color));
    color: var(--box-fg-color);
    opacity: var(--box-hdr-opacity);
}

Les variables sont définies dans l’entête de la feuille de style (section :root) :

style.css
:root {

  --page-bg-color: var(--c-page-bg-color,#aaabad);
  --wrap-bg-color: var(--c-wrap-bg-color,#FFFFFF);
  --wrap-fg-color: var(--c-wrap-fg-color,#575757);
  
  --heading-fg-color:    var(--c-heading-fg-color,#808080);
  --heading-font-family: Sansation, "Century Gothic", Verdana, Arial, sans-serif;
  
  --anchor-color:  var(--c-anchor-color,#2a799e);
  
  --sys-box-bg-color: 168, 132, 72;
  
  --box-bg-color:    var(--c-box-bg-color,var(--sys-box-bg-color));
  --box-fg-color:    #FFFFFF;
  --box-hdr-opacity: var(--c-box-hdr-opacity,0.6);

}

body { … }
…

Dans la syntaxe exemple ci-dessous : la valeur de la variable --anchor-color a la valeur de la variable --c-anchor-color si elle existe, sinon la valeur de secours #2a799e. La nomenclature --c- est liée au mot clé custom.

--anchor-color:  var(--c-anchor-color,#2a799e);

Donc 2 feuilles de style sont préparées pour définir les variables --c-% :

style-default.css (mode jour)
:root {
 --c-page-bg-color: #aaabad;
 --c-wrap-bg-color: #FFFFFF;
 --c-wrap-fg-color: #575757;
 --c-heading-fg-color: #808080;
 --c-anchor-color: #2a799e;
 --c-box-hdr-opacity: 0.6;
}
style-okaidia.css (mode nuit)
:root {
 --c-page-bg-color: #596673; 
 --c-wrap-bg-color: #212529;
 --c-wrap-fg-color: #d9d9d9;
 --c-heading-fg-color: #d9d9d9; 
 --c-anchor-color: #7299df;
 --c-box-hdr-opacity: 0.7;
}

Selon le choix de l’utilisateur, la feuille de style appropriée est chargée avant la principale. Pour gérer ultérieurement la feuille de style personnalisée avec Javascript, un identifiant est appliqué.

mode Jour
<head>
 …
 <link href="/css/custom/style-default.css" rel="stylesheet" id="customStyle">
 <link href="/css/style.css" rel="stylesheet">
 …
</head>
mode Nuit
<head>
 …
 <link href="/css/custom/style-okadia.css" rel="stylesheet" id="customStyle">
 <link href="/css/style.css" rel="stylesheet">
 …
</head>

Action de la case à cocher

Sur l’élément checkbox, un écouteur est ajouté pour l’événement click lorque le contenu du document est chargé (événement DOMContentLoaded) :

<script>
    setCustomStyle=function(customStyle) {…};
    
    window.addEventListener('DOMContentLoaded', function() {
      cb = document.querySelector('div[class*="toggle-container"] > input[type="checkbox"]');
      if (cb !==null ) {
        cb.addEventListener('click',function() {
                st = (this.checked) ? 'okaidia' : 'default';
                setCustomStyle(st);
        });
      }
    });        
</script>

La fonction setCustomStyle va :

  • appliquer la variable cookie customStyle à okaidia ou default.
  • supprimer la feuille de style personnalisée existante puis ajouter celle qui est sélectionnée dans l’arbre DOM, forçant ainsi la réévaluation des variables et la page à se repeindre.
setCustomStyle=function(customStyle) {

  /** Mise à jour de la valeur de la variable cookie customStyle */
  let d = new Date();
  let expiredays=60;
	d.setTime(d.getTime() + (expiredays*24*60*60*1000));
	let expires = 'expires='+ d.toUTCString();
	document.cookie = `customStyle=${customStyle};${expires};path=/`;
  
  /** Remplacement des feuilles de styles personnalisée */
  if (document.querySelector('link[id="customStyle"]')) {
    document.head.removeChild(document.querySelector('link[id="customStyle"]'));
  }
  lk = document.createElement('link');
  lk.setAttribute('id','customStyle');
  lk.setAttribute('rel','stylesheet');
  lk.setAttribute('href',`/css/custom/style-${customStyle}.css`);
  document.head.appendChild(lk);

};

C’est fini ? Pas tout à fait, encore 2 détails à gérer :

  • Conservation du choix de l’utilisateur en cas de rechargement ou de revisite de la page par ce dernier.
  • Ajustement de PrismJS syntax highlighter utilisé dans cette page.

Conservation du choix de l’utilisateur

La feuille de style personnalisée doit en fait être ajoutée dynamiquement avant la feuille de style principale : seul contexte dans lequel la feuille de style appropriée est chargée si l’utilisateur recharge ou visite à nouveau la page, on conserve ainsi le choix utilisateur.

Le morceau de code nécessaire vérifie si la variable cookie customStyle existe. Si elle n’existe pas, la feuille de style default est chargée, sinon la feuille de style personnalisée en fonction de la valeur de la variable cookie customStyle.

Avec PHP et la variable globale $_COOKIE :

<head>
 …
    <?php
      $customStyle = isset($_COOKIE['customStyle']) ? $_COOKIE['customStyle'] : 'default';
      echo "<link id=\"customStyle\" href=\"/css/custom/style-".$customStyle.".css\" rel=\"stylesheet\">";       
    ?>

 <link href="/css/style.css" rel="stylesheet">
 …
</head>

Avec Javascript en mode synchrone (un peu plus complexe si il n’existe pas encore de fonction générique de décodage des variables cookie) :

<head>
 …
 <script>
      cookie_get=function(cn) {
        let decodedCookie = decodeURIComponent(document.cookie).replace(new RegExp('; ', 'g'),';');
        let ca = decodedCookie.split(';').filter(function(cookie) { return cookie.split('=')[0] == cn; });
        if (ca.length == 1) { return ca[0].split('=')[1]; }
        return false;	 
      };
      
      var customStyle = (cookie_get('customStyle')) ? cookie_get('customStyle') : 'default';
      lk=document.createElement('link');
      lk.setAttribute('rel','stylesheet');
      lk.setAttribute('id','customStyle');
      lk.setAttribute('href',`/css/custom/style-${customStyle}.css`);
      document.head.appendChild(lk);
 </script>

 <link href="/css/style.css" rel="stylesheet">
 …
</head>

À présent la case est cochée si le style n’est pas default.

PHP :

                       <div class="toggle-container">
              <input type="checkbox" <?php if (isset($customStyle) && $customStyle != 'default') { echo " checked "; } ?> >
              <div class="slider round"></div>
            </div>
          

Javascript :

<div class="toggle-container">
    <input type="checkbox">
    <div class="slider round"></div>
</div>
<script>
    if (typeof(customStyle) != 'undefined' && customStyle !=='default') {
        document.querySelector('div[class*="toggle-container"] > input[type="checkbox"]').checked=true;
    }
</script>

Ajustement pour PrismJS syntax highlighter

Les noms des feuilles de style default et okaidia n’ont pas été choisies au hasard. Ils correspondent aux noms des fichiers CSS de la librairie PrismJS syntax highlighter.

Les feuilles de style de PrismJS (prism.default.css, prism.okaidia.css…) sont installées dans /common/external/prism, aussi améliorons le code de la fonction pour mettre à jour également la feuille de style de PrismJS :

setCustomStyle=function(customStyle) {

  /** Set Cookie value */
  …
  
  /** Replace custom stylesheet */
  if (document.querySelector('link[id="customStyle"]')) {
    document.head.removeChild(document.querySelector('link[id="customStyle"]'));
  }
  lk = document.createElement('link');
  lk.setAttribute('id','customStyle');
  lk.setAttribute('rel','stylesheet');
  lk.setAttribute('href',`/css/custom/style-${customStyle}.css`);
  document.head.appendChild(lk);
  
  /** Update PrismJS stylesheet */
  cprism=document.querySelector(`link[rel="stylesheet"][href*="/common/external/prism/prism"]`);
  if (cprism !== null) {
    document.head.removeChild(cprism);
    lkp = document.createElement('link');
    lkp.setAttribute('rel','stylesheet');
    lkp.setAttribute('href',`/common/external/prism/prism.${customStyle}.css`);
    document.head.appendChild(lkp);
  }
  
};

Évidemment, la bonne feuille de style pour PrismJS doit être chargée si l’utilisateur recharge ou visite à nouveau la page en fonction de la variable cookie customStyle si elle existe.