Fermer

mars 25, 2024

Définition et conservation des préférences de schéma de couleurs avec CSS et une « touche » de JavaScript —

Définition et conservation des préférences de schéma de couleurs avec CSS et une « touche » de JavaScript —


De nombreux sites Web modernes donnent aux utilisateurs le pouvoir de définir une préférence de jeu de couleurs spécifique au site. Une implémentation de base est simple avec JavaScript : écoutez lorsqu’un utilisateur modifie une case à cocher ou clique sur un bouton, active une classe (ou un attribut) sur le <body> élément en réponse et écrivez les styles de cette classe pour remplacer la conception par un jeu de couleurs différent.

Le nouveau CSS :has() pseudo-classe, pris en charge par les principaux navigateurs depuis décembre 2023, ouvre de nombreuses portes aux développeurs front-end. Je suis particulièrement enthousiaste à l’idée de l’exploiter pour modifier l’interface utilisateur en réponse à l’interaction de l’utilisateur. sans JavaScript. Là où auparavant nous utilisions JavaScript pour basculer les classes ou les attributs (ou pour définir directement les styles), nous pouvons désormais associer :has() sélecteurs avec les éléments interactifs natifs de HTML.

La prise en charge d’une préférence de jeu de couleurs, comme le « Mode sombre », est un excellent cas d’utilisation. Nous pouvons utiliser un <select> élément n’importe où qui bascule les jeux de couleurs en fonction de la sélection <option> – aucun JavaScript n’est nécessaire, sauf une pincée pour enregistrer le choix de l’utilisateur, que nous reviendrons plus loin.

Respecter les préférences système

Tout d’abord, nous prendrons en charge les préférences de jeu de couleurs à l’échelle du système d’un utilisateur en adoptant d’abord une approche axée sur le « Mode Lumière ». En d’autres termes, nous commençons par défaut par une palette de couleurs claires et l’échangeons contre une palette de couleurs sombres pour les utilisateurs qui le préfèrent.

Le prefers-color-scheme La fonction multimédia détecte les préférences système de l’utilisateur. Enveloppez les styles « sombres » dans un prefers-color-scheme: dark requête médiatique.

selector {
  /* light styles */

  @media (prefers-color-scheme: dark) {
    /* dark styles */
  }
}

Ensuite, définissez le color-scheme propriété pour correspondre à la palette de couleurs préférée. Paramètre color-scheme: dark fait passer le navigateur dans son mode sombre intégré, qui comprend un arrière-plan noir par défaut, un texte blanc par défaut, des styles « sombres » pour les barres de défilement et d’autres éléments difficiles à cibler avec CSS, et plus encore. J’utilise des variables CSS pour laisser entendre que la valeur est dynamique – et parce que j’aime l’expérience des outils de développement de navigateur – mais simplement color-scheme: light et color-scheme: dark fonctionnerait bien.

:root {
  /* light styles here */
  color-scheme: var(--color-scheme, light);
  
  /* system preference is "dark" */
  @media (prefers-color-scheme: dark) {
    --color-scheme: dark;
    /* any additional dark styles here */
  }
}

Donner le contrôle aux utilisateurs

Maintenant, pour soutenir primordial la préférence système, permet aux utilisateurs de choisir entre des schémas de couleurs claires (par défaut) et sombres au niveau de la page.

HTML possède des éléments natifs pour gérer les interactions des utilisateurs. Utiliser l’un de ces contrôles, plutôt que, disons, un <div> nest, améliore les chances que les utilisateurs de technologies d’assistance vivent une bonne expérience. je vais utiliser un <select> menu avec des options pour « système », « clair » et « sombre ». Un groupe de <input type="radio"> fonctionnerait également si vous vouliez les options directement en surface au lieu d’un menu déroulant.

<select id="color-scheme">
  <option value="system" selected>System</option>
  <option value="light">Light</option>
  <option value="dark">Dark</option>
</select>

Avant que CSS ne gagne :has()répondant à la sélection de l’utilisateur <option> JavaScript requis, par exemple, la définition d’un écouteur d’événement sur le <select> pour activer une classe ou un attribut <html> ou <body>.

Mais maintenant que nous avons :has(), nous pouvons désormais le faire avec CSS seul ! Vous économiserez une partie de votre budget de performances sur un script en mode sombre, et le contrôle fonctionnera même pour les utilisateurs qui ont désactivé JavaScript. Et tous les utilisateurs « non-JS » du projet seront satisfaits.

Ce dont nous avons besoin, c’est d’un sélecteur qui s’applique à la page lorsqu’elle :has() un select menu avec un particulier [value]:checked. Traduisons cela en CSS :

:root:has(select option[value="dark"]:checked)

Nous utilisons par défaut une palette de couleurs claires, il suffit donc de prendre en compte deux scénarios possibles de palettes de couleurs sombres :

  1. La préférence de couleur au niveau de la page est « système » et la préférence au niveau du système est « sombre ».
  2. La préférence de couleur au niveau de la page est « sombre ».

Le premier est une itération de notre modèle prenant en compte les préférences de page. prefers-color-scheme: dark cas. Une préférence « sombre » au niveau du système ne suffit plus à justifier des styles sombres ; nous avons besoin d’une préférence « sombre » au niveau du système et d’une préférence « suivre la préférence au niveau du système » au niveau de la page. Nous allons emballer le prefers-color-scheme styles de schéma sombre de requête multimédia avec le :has() sélecteur nous venons d’écrire :

:root {
  /* light styles here */
  color-scheme: var(--color-scheme, light);
    
  /* page preference is "system", and system preference is "dark" */
  @media (prefers-color-scheme: dark) {
    &:has(#color-scheme option[value="system"]:checked) {
      --color-scheme: dark;
      /* any additional dark styles, again */
    }
  }
}

Notez que j’utilise Imbrication CSS dans ce dernier extrait. Référence 2023 est-il classé comme « Nouvellement disponible sur les principaux navigateurs », ce qui signifie que le support est bon, mais au moment de la rédaction, le support sur les navigateurs Android n’est pas inclus dans Ensemble de navigateur principal de Baseline est limité. Vous pouvez obtenir le même résultat sans imbrication.

:root {
  /* light styles */
  color-scheme: var(--color-scheme, light);
    
  /* page preference is "dark" */
  &:has(#color-scheme option[value="dark"]:checked) {
    --color-scheme: dark;
    /* any additional dark styles */
  }
}

Pour le deuxième scénario du mode sombre, nous utiliserons presque exactement le même :has() sélecteur comme nous l’avons fait pour le premier scénario, en vérifiant cette fois si l’option « sombre » – plutôt que l’option « système » – est sélectionnée :

:root {
  /* light styles */
  color-scheme: var(--color-scheme, light);
    
  /* page preference is "dark" */
  &:has(#color-scheme option[value="dark"]:checked) {
    --color-scheme: dark;
    /* any additional dark styles */
  }
    
  /* page preference is "system", and system preference is "dark" */
  @media (prefers-color-scheme: dark) {
    &:has(#color-scheme option[value="system"]:checked) {
      --color-scheme: dark;
      /* any additional dark styles, again */
    }
  }
}

Désormais, les styles de la page répondent aux modifications apportées aux paramètres système des utilisateurs. et interaction de l’utilisateur avec l’interface utilisateur des préférences de couleur de la page — le tout avec CSS !

Mais les couleurs changent immédiatement. Facilitons la transition.

Respecter les préférences de mouvement

Les changements de style instantanés peuvent sembler inélégants dans certains cas, et celui-ci en fait partie. Alors, appliquons une transition CSS sur le :root pour « faciliter » le basculement entre les combinaisons de couleurs. (Styles de transition au :root se répercutera sur le reste de la page, ce qui peut nécessiter l’ajout de transition: none ou d’autres remplacements de transition.)

Notez que le CSS color-scheme la propriété ne prend pas en charge les transitions.

:root {
  transition-duration: 200ms;
  transition-property: /* properties changed by your light/dark styles */;
}

Tous les utilisateurs ne considéreront pas l’ajout d’une transition comme une amélioration bienvenue. Interroger le prefers-reduced-motion La fonctionnalité multimédia nous permet de prendre en compte les préférences de mouvement d’un utilisateur. Si la valeur est définie sur reducepuis on supprime le transition-duration pour éliminer les mouvements indésirables.

:root {
  transition-duration: 200ms;
  transition-property: /* properties changed by your light/dark styles */;
    
  @media screen and (prefers-reduced-motion: reduce) {
    transition-duration: none;
  }
}

Les transitions peuvent également produire une mauvaise expérience utilisateur sur les appareils qui affichent les modifications lentement, par exemple ceux dotés d’écrans à encre électronique. Nous pouvons étendre notre requête média « aucune condition de mouvement » pour en tenir compte avec la update fonctionnalité multimédia. Si sa valeur est slowpuis on supprime le transition-duration.

:root {
  transition-duration: 200ms;
  transition-property: /* properties changed by your light/dark styles */;
    
  @media screen and (prefers-reduced-motion: reduce), (update: slow) {
    transition-duration: 0s;
  }
}

Essayons ce que nous avons jusqu’à présent dans la démo suivante. Notez que, pour contourner color-schemeEn raison du manque de prise en charge des transitions, j’ai explicitement stylisé les propriétés qui doivent effectuer une transition lors des changements de thème.

Voir le stylo [CSS-only theme switcher (requires :has()) [forked]](https://codepen.io/smashingmag/pen/YzMVQja) par Henri.

Voir le stylo Sélecteur de thème CSS uniquement (nécessite :has()) [forked] par Henri.

Pas mal! Mais que se passe-t-il si l’utilisateur actualise les pages ou accède à une autre page ? Le rechargement efface effectivement la sélection de formulaire de l’utilisateur, obligeant l’utilisateur à refaire la sélection. Cela peut être acceptable dans certains contextes, mais cela risque d’aller à l’encontre des attentes des utilisateurs. Introduisons JavaScript pour une touche d’amélioration progressive sous la forme de…

Persistance

Voici une implémentation JavaScript vanille. C’est un point de départ naïf : les fonctions et les variables ne sont pas encapsulées mais sont plutôt des propriétés sur window. Vous souhaiterez l’adapter d’une manière qui correspond aux conventions, au cadre, à la bibliothèque, etc. de votre site.

Lorsque l’utilisateur modifie le schéma de couleurs du <select> menu, nous stockerons le sélectionné <option> valeur dans un nouveau localStorage article appelé "preferredColorScheme". Lors des chargements de pages suivants, nous vérifierons localStorage pour le "preferredColorScheme" article. S’il existe, et si sa valeur correspond à l’une des options de contrôle du formulaire, nous restaurons la préférence de l’utilisateur en mettant à jour par programmation la sélection du menu.

/*
 * If a color scheme preference was previously stored,
 * select the corresponding option in the color scheme preference UI
 * unless it is already selected.
 */
function restoreColorSchemePreference() {
  const colorScheme = localStorage.getItem(colorSchemeStorageItemName);

  if (!colorScheme) {
    // There is no stored preference to restore
    return;
  }

  const option = colorSchemeSelectorEl.querySelector(`[value=${colorScheme}]`);  

  if (!option) {
    // The stored preference has no corresponding option in the UI.
    localStorage.removeItem(colorSchemeStorageItemName);
    return;
  }

  if (option.selected) {  
    // The stored preference's corresponding menu option is already selected
    return;
  }

  option.selected = true;
}

/*
 * Store an event target's value in localStorage under colorSchemeStorageItemName
 */
function storeColorSchemePreference({ target }) {
  const colorScheme = target.querySelector(":checked").value;
  localStorage.setItem(colorSchemeStorageItemName, colorScheme);
}

// The name under which the user's color scheme preference will be stored.
const colorSchemeStorageItemName = "preferredColorScheme";

// The color scheme preference front-end UI.
const colorSchemeSelectorEl = document.querySelector("#color-scheme");

if (colorSchemeSelectorEl) {
  restoreColorSchemePreference();

  // When the user changes their color scheme preference via the UI,
  // store the new preference.
  colorSchemeSelectorEl.addEventListener("input", storeColorSchemePreference);
}

Essayons ça. Ouvrez cette démo (peut-être dans une nouvelle fenêtre), utilisez le menu pour modifier la palette de couleurs, puis actualisez la page pour voir votre préférence persister :

Voir le stylo [CSS-only theme switcher (requires :has()) with JS persistence [forked]](https://codepen.io/smashingmag/pen/GRLmEXX) par Henri.

Voir le stylo Sélecteur de thème CSS uniquement (nécessite :has()) avec persistance JS [forked] par Henri.

Si la préférence de jeu de couleurs de votre système est « clair » et que vous définissez le jeu de couleurs de la démo sur « sombre », vous pouvez obtenir les styles du mode clair pendant un moment immédiatement après avoir rechargé la page avant que les styles du mode sombre n’interviennent. En effet, CodePen charge son posséder JavaScript avant les scripts de la démo. C’est hors de mon contrôle, mais vous pouvez veiller à améliorer cette persistance sur vos projets.

Considérations sur les performances de persistance

Là où les choses peuvent devenir délicates, c’est dans la restauration des préférences de l’utilisateur immédiatement après le chargement de la page. Si la préférence de jeu de couleurs dans localStorage est différent de la préférence de jeu de couleurs au niveau du système de l’utilisateur, il est possible que l’utilisateur voie le jeu de couleurs des préférences système avant que la préférence au niveau de la page ne soit restaurée. (Les utilisateurs qui ont sélectionné l’option « Système » ne recevront jamais ce flash ; ceux dont les paramètres système correspondent à l’option sélectionnée dans le contrôle de formulaire non plus.)

Si votre implémentation montre un « flash de thème de couleur inexact », où se situe le problème ? De manière générale, plus les scripts apparaissent tôt sur la page, plus le risque est faible. La « meilleure option » pour vous dépendra bien sûr de votre stack spécifique.

Qu’en est-il des navigateurs qui ne prennent pas en charge :has()?

Tous les principaux navigateurs sont pris en charge :has() aujourd’hui Appuyez-vous sur les plateformes modernes si vous le pouvez. Mais si vous devez prendre en compte les navigateurs existants, comme Internet Explorer, vous pouvez suivre deux directions : soit masquer ou supprimer le sélecteur de couleurs pour ces navigateurs, soit utiliser davantage JavaScript.

Si vous considérez la prise en charge des schémas de couleurs comme une amélioration progressive, vous pouvez entièrement masquer l’interface utilisateur de sélection dans les navigateurs qui ne prennent pas en charge :has():

@supports not selector(:has(body)) {
  @media (prefers-color-scheme: dark) {
    :root {
      /* dark styles here */
    }
  }

  #color-scheme {
    display: none;
  }
}

Sinon, vous devrez vous appuyer sur une solution JavaScript non seulement pour la persistance mais aussi pour les fonctionnalités de base. Revenez à cet écouteur d’événement traditionnel en basculant une classe ou un attribut.

Les astuces CSS « Guide complet du mode sombre» détaille plusieurs approches alternatives que vous pourriez également envisager lorsque vous travaillez sur l’héritage.

Éditorial fracassant
(gg, ouais)




Source link