Un texte automatiquement contrasté avec le fond, uniquement en CSS

Bonjour à tous ! Peut-être vous êtes-vous déjà demandé comment avoir une couleur de texte automatique par rapport au fond, la plus contrastée possible pour répondre à des critères d'accessibilité ? Non ? Ça ne m'étonne pas, car en général on maîtrise aussi bien la couleur de fond que la couleur du texte. Mais si vous êtes ici, c'est soit que vous faites partie des rares personnes ayant cette problématique, soit que vous êtes juste curieux ou curieuse !

Petit rappel d'accessibilité

La plupart des bonnes pratiques en termes d'accessibilité sont dictées, juste après le bon sens, par les WCAG du W3C. Celles-ci sont également définies en niveaux croissants : "A", "AA" et "AAA". Pour obtenir au moins le niveau "AA", vos textes de petite taille (moins de 18pt, ou moins de 14pt si gras) doivent avoir un rapport de contraste d'au moins 4.5 avec la couleur de fond. Si ce ratio dépasse 7, vous pouvez prétendre au niveau "AAA". Si vous souhaitez davantage d'informations à ce sujet, vous pouvez vous documenter à la source.

Pour obtenir à la fois le ratio de contraste et l'aspect du rendu, je ne peux que vous recommander l'excellent outil de Lea Verou : contrast-ratio.com.

Déroulé du plan : la partie théorique

Nous allons partir sur plusieurs constats :

  • les couleurs offrant les contrastes les plus extrêmes sont le noir et le blanc, l'objectif sera donc d'avoir un texte noir ou blanc en fonction de la couleur de fond. Pas d'entre-deux !
  • il semble que la déclaration de couleurs en rgb accepte des valeurs négatives ou plus grandes que 255 (testé sur Edge, Chrome et Firefox). Je ne suis pas fan des valeurs hors normes, mais là ça va nous arranger.
  • le W3C nous fournit le calcul de la luminosité d'une couleur à partir de ses composantes rouge, vert et bleu :
    ((rouge x 299) + (vert x 587) + (bleu x 114)) / 1000

Concernant ce dernier point, je n'ai pas le détail de l'origine de cette formule. Mais j'imagine que c'est basé sur l'intensité d'une couleur, c'est à dire que le bleu est visuellement plus foncé que le vert, il a donc un poids plus élevé que le vert, et que donc pour équilibrer ce rapport il est proportionnellement réduit dans le poids total du calcul. À l'issue de ce calcul on se retrouve donc avec une valeur de entre 0 (noir) et 255 (blanc). Par exemple les trois couleurs primaires (en RVB) ont une luminosité de 76,245 pour le rouge, 149,685 pour le vert et 29,07 pour le bleu.

En observant ces valeurs, on se rend compte que le vert est difficilement lisible si vous consultez mon site avec son thème clair, et que sa valeur de luminosité dépasse la moitié de la valeur maximale (127,5, que l'on va arrondir à 128 parce qu'on aime les multiples de 8 en informatique). On va donc faire en sorte que quand la luminosité d'une couleur de fond dépasse 128, le texte sera noir, sinon il sera blanc. Le texte sur fond vert sera donc noir, et sur fond rouge ou bleu il sera blanc.

Les mains dans le cambouis : la mise en pratique

Nous y voilà, il ne nous reste plus qu'à assembler les morceaux ! Pour cela nous allons avoir besoin d'utiliser les variables CSS, vous allez donc peut-être vouloir filtrer en amont avec un @supports. Et pour commencer, nous allons déclarer les valeurs de rouge, vert et bleu qui seront utilisées en couleur de fond. Ici, partons sur un fond blanc pour l'élément ayant la classe autocouleur :

.autocouleur{
    --rouge: 255;
    --vert: 255;
    --bleu: 255;
    background: rgb( var(--rouge), var(--vert), var(--bleu) );
}

À ce stade, notre .autocouleur a juste un fond blanc, rien de bien transcendant mais la base y est. Nous allons ajouter à cette déclaration CSS le calcul de la luminosité de la couleur :

.autocouleur{
    --rouge: 255;
    --vert: 255;
    --bleu: 255;
    background: rgb( var(--rouge), var(--vert), var(--bleu) );
    --luminosite: calc( ( var(--rouge)*299 + var(--vert)*587 + var(--bleu)*114 ) / 1000 );
}

Rien ce change visuellement parlant, il va falloir rajouter le calcul de la couleur de texte à partir de cette luminosité. Ici, l'objectif est d'avoir une valeur négative (pour une couleur de texte noire) si la luminosité est supérieure à 128, et une valeur supérieure à 255 (pour une couleur de texte blanche) si la luminosité est inférieure à 128. On pourrait donc partir sur : --couleur: calc( (var(--luminosite) - 128) * -1 );.

Mais ce calcul se contente d'avoir des valeurs comprises entre -127 et 128, donc une couleur de texte entre noir et gris moyen, il faut pouvoir étendre ces valeurs de sorte à ce qu'aucune valeur ne tombe entre 0 et 255, de sorte à ne jamais avoir de gris. On multiplie donc le total par 255, ainsi si la luminosité est de 0, la couleur sera 0, et si elle de 1 la couleur sera 255 (les valeurs en-dessous de 0 et au-dessus de 1 ne posant plus de problèmes). On en arrive donc à : --couleur: calc( (var(--luminosite) - 128) * -255 );.

Maaaais... il reste encore un détail, car comme le calcul de la luminosité se termine par une division par 1000, il peut y avoir jusqu'à trois décimales dans le résultat, y compris entre 0 et 1, résultant toujours à des valeurs comprises entre 0 et 255, et donc du gris. Par exemple si je prends la couleur rgb(195,99,99), sa luminosité de 127,704 aboutit à une valeur de couleur de 75,48 après ce dernier calcul, donc le texte serait gris. Pour remédier à ça, on peut remultiplier par 1000 pour enlever les décimales. On en arrive à des valeurs de couleurs énormes, mais comme évoqué lors du deuxième constat de la partie théorique ça ne pose pas de problème (même si ça peut faire grincer des dents) puisque, contrairement à certaines lessives, en CSS on ne peut pas être plus noir que noir ni plus blanc que blanc. Notre calcul de couleur serait donc le suivant, suivi de l'application de cette valeur avec la propriété color :

.autocouleur{
    --rouge: 255;
    --vert: 255;
    --bleu: 255;
    background: rgb( var(--rouge), var(--vert), var(--bleu) );
    --luminosite: calc( ( var(--rouge)*299 + var(--vert)*587 + var(--bleu)*114 ) / 1000 );
    --couleur: calc( (var(--luminosite) - 128) * -255000);
    color: rgb(var(--couleur), var(--couleur), var(--couleur));
}

Exemple d'utilisation

Tout y est, il ne reste alors plus qu'à modifier les valeurs de rouge, vert et bleu, le texte s'adaptera alors au fond avec un contraste adapté, et uniquement en CSS ! Vous pouvez tester sur ce paragraphe en changeant sa couleur de fond à l'aide du sélecteur suivant. Bon, pour cet exemple il faut bien du javascript pour modifier les valeurs du CSS, mais le but de cet article est d'adapter la couleur du texte au fond, pas de modifier le fond, donc objectif 100% CSS réussi :)

Si vous avez trouvé cet article intéressant, pourquoi ne pas le partager ? :)
Facebook Messenger Twitter LinkedIn Viadeo Reddit Email

Vous avez une remarque, une précision à apporter, ou quelque-chose vous échappe ?
N'hésitez pas à participer à cet article, cet espace est pour vous !

Personne n'a encore commenté cet article.

Mais il faut bien un début à tout, pas vrai ?

Commenter, poser une question...

 

Remonter