Cet article explique pourquoi et comment nous avons implémenté la simulation de déficience visuelle dans les outils de développement et le moteur de rendu Blink.
Arrière-plan: un mauvais contraste des couleurs
Le texte à faible contraste est le problème d'accessibilité le plus courant, qui peut être détecté automatiquement sur le Web.
Selon l'analyse d'accessibilité de WebAIM sur les 1 million de premiers sites Web, plus de 86% des pages d'accueil présentent un contraste faible. En moyenne, chaque page d'accueil comporte 36 instances distinctes de texte à faible contraste.
Utiliser les outils pour les développeurs pour identifier, comprendre et résoudre les problèmes de contraste
Les outils pour les développeurs Chrome peuvent aider les développeurs et les concepteurs à améliorer le contraste et à choisir des jeux de couleurs plus accessibles pour les applications Web:
- L'info-bulle du mode d'inspection qui s'affiche en haut de la page Web indique le rapport de contraste des éléments de texte.
- Le sélecteur de couleurs de DevTools signale les mauvais rapports de contraste pour les éléments textuels, affiche la ligne de contraste recommandée pour vous aider à sélectionner manuellement de meilleures couleurs, et peut même suggérer des couleurs accessibles.
- Le panneau "CSS Overview" (Aperçu CSS) et le rapport d'audit d'accessibilité Lighthouse indiquent les éléments de texte à faible contraste trouvés sur votre page.
Nous avons récemment ajouté un nouvel outil à cette liste, qui est un peu différent des autres. Les outils ci-dessus visent principalement à afficher des informations sur le rapport de contraste et à vous proposer des options pour le corriger. Nous avons réalisé qu'il manquait encore aux développeurs un moyen de mieux comprendre ce domaine de problèmes. Pour y remédier, nous avons implémenté la simulation de déficience visuelle dans l'onglet "Rendering" (Rendu) des outils de développement.
Dans Puppeteer, la nouvelle API page.emulateVisionDeficiency(type)
vous permet d'activer ces simulations de manière programmatique.
Déficiences de la vision des couleurs
Près d'une personne sur 20 souffre d'un trouble de la perception des couleurs (également appelé "daltonisme", un terme moins précis). Ces déficiences rendent plus difficile la distinction entre les couleurs, ce qui peut amplifier les problèmes de contraste.
En tant que développeur ayant une vision régulière, vous constaterez peut-être que les outils de développement affichent un rapport de contraste médiocre pour les paires de couleurs qui vous conviennent visuellement. En effet, les formules de rapport de contraste tiennent compte de ces déficiences visuelles. Il peut arriver dans certains cas que vous puissiez lire du texte à faible contraste, à la différence des personnes qui souffrent de ces troubles de la vision.
En permettant aux concepteurs et aux développeurs de simuler l'effet de ces déficiences visuelles sur leurs propres applications Web, notre objectif est de fournir l'élément manquant: non seulement les outils de développement peuvent vous aider à identifier et à résoudre les problèmes de contraste, mais aussi à les comprendre !
Simuler des déficiences de vision des couleurs avec HTML, CSS, SVG et C++
Avant de nous plonger dans l'implémentation de notre fonctionnalité dans le moteur de rendu Blink, il est utile de comprendre comment implémenter une fonctionnalité équivalente à l'aide de la technologie Web.
Vous pouvez considérer chacune de ces simulations de déficience de la vision des couleurs comme une superposition recouvrant toute la page. La plate-forme Web dispose d'un moyen de le faire: les filtres CSS. Avec la propriété CSS filter
, vous pouvez utiliser certaines fonctions de filtre prédéfinies, telles que blur
, contrast
, grayscale
, hue-rotate
et bien d'autres. Pour un contrôle encore plus poussé, la propriété filter
accepte également une URL pouvant pointer vers une définition de filtre SVG personnalisée:
<style>
:root {
filter: url(#deuteranopia);
}
</style>
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
L'exemple ci-dessus utilise une définition de filtre personnalisée basée sur une matrice de couleurs. Conceptuellement, la valeur de couleur [Red, Green, Blue, Alpha]
de chaque pixel est multipliée par une matrice pour créer une nouvelle couleur [R′, G′, B′, A′]
.
Chaque ligne de la matrice contient 5 valeurs: un multiplicateur pour (de gauche à droite) R, V, B et A, ainsi qu'une cinquième valeur pour une valeur de changement constant. Elle comporte quatre lignes: la première ligne de la matrice est utilisée pour calculer la nouvelle valeur de rouge, la deuxième pour le vert, la troisième pour le bleu et la dernière pour l'alpha.
Vous vous demandez peut-être d'où viennent les chiffres exacts de notre exemple. Qu'est-ce qui fait que cette matrice de couleurs est une bonne approximation de la deutéranopie ? La réponse est: la science ! Les valeurs sont basées sur un modèle de simulation physiologiquement précis du déficit de la vision des couleurs par Machado, Oliveira et Fernandes.
Nous avons donc ce filtre SVG, et nous pouvons maintenant l'appliquer à des éléments arbitraires de la page à l'aide de CSS. Nous pouvons répéter le même schéma pour d'autres déficiences visuelles. Voici une démonstration:
Si nous le souhaitions, nous pourrions créer notre fonctionnalité DevTools comme suit: lorsque l'utilisateur émule un trouble de la vision dans l'interface utilisateur de DevTools, nous injectons le filtre SVG dans le document inspecté, puis nous appliquons le style de filtre à l'élément racine. Toutefois, cette approche présente plusieurs problèmes:
- La page peut déjà comporter un filtre sur son élément racine, que notre code peut ensuite remplacer.
- La page peut déjà contenir un élément avec
id="deuteranopia"
, ce qui entre en conflit avec notre définition de filtre. - La page peut s'appuyer sur une certaine structure DOM et l'insertion du
<svg>
dans le DOM peut entraîner une violation de ces hypothèses.
En dehors des cas particuliers, le principal problème de cette approche est que nous apporterions des modifications observables par programmation à la page. Si un utilisateur de DevTools inspecte le DOM, il peut soudainement voir un élément <svg>
qu'il n'a jamais ajouté ou un filter
CSS qu'il n'a jamais écrit. Ce serait déroutant ! Pour implémenter cette fonctionnalité dans DevTools, nous avons besoin d'une solution qui ne présente pas ces inconvénients.
Voyons comment rendre cette fonctionnalité moins intrusive. Cette solution comporte deux parties que nous devons masquer: 1) le style CSS avec la propriété filter
et 2) la définition du filtre SVG, qui fait actuellement partie du DOM.
<!-- Part 1: the CSS style with the filter property -->
<style>
:root {
filter: url(#deuteranopia);
}
</style>
<!-- Part 2: the SVG filter definition -->
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
Éviter la dépendance SVG dans le document
Commençons par la partie 2: comment éviter d'ajouter le SVG au DOM ? Vous pouvez par exemple le déplacer vers un fichier SVG distinct. Nous pouvons copier le <svg>…</svg>
du code HTML ci-dessus et l'enregistrer en tant que filter.svg
, mais nous devons d'abord apporter quelques modifications. Le SVG intégré dans le code HTML respecte les règles d'analyse HTML. Cela signifie que vous pouvez omettre les guillemets autour des valeurs d'attribut dans certains cas. Toutefois, les fichiers SVG distincts sont censés être des fichiers XML valides, et l'analyse XML est beaucoup plus stricte que l'analyse HTML. Voici à nouveau notre extrait SVG-HTML:
<svg>
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
Pour que ce fichier SVG autonome (et donc XML) soit valide, nous devons apporter quelques modifications. Pouvez-vous deviner lequel ?
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000" />
</filter>
</svg>
La première modification concerne la déclaration d'espace de noms XML en haut. Le second ajout est le "solidus", la barre oblique qui indique que la balise <feColorMatrix>
ouvre et ferme l'élément. Cette dernière modification n'est pas vraiment nécessaire (nous pourrions nous contenter de la balise de fermeture </feColorMatrix>
explicite), mais comme XML et SVG-in-HTML sont compatibles avec cet abbréviateur />
, nous pouvons tout aussi bien l'utiliser.
Quoi qu'il en soit, avec ces modifications, nous pouvons enfin enregistrer le fichier au format SVG valide et y faire référence à partir de la valeur de la propriété CSS filter
dans notre document HTML:
<style>
:root {
filter: url(filters.svg#deuteranopia);
}
</style>
Hourra ! Nous n'avons plus besoin d'injecter de SVG dans le document. C'est déjà beaucoup mieux. Mais… nous dépendons désormais d'un fichier distinct. Il s'agit toujours d'une dépendance. Pouvons-nous nous en débarrasser ?
Il s'avère que nous n'avons pas besoin d'un fichier. Nous pouvons encoder l'intégralité du fichier dans une URL à l'aide d'une URL de données. Pour ce faire, nous prenons littéralement le contenu du fichier SVG que nous avions auparavant, ajoutons le préfixe data:
, configurons le type MIME approprié, et nous obtenons une URL de données valide qui représente le même fichier SVG:
data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="deuteranopia">
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000
0.280 0.673 0.047 0.000 0.000
-0.012 0.043 0.969 0.000 0.000
0.000 0.000 0.000 1.000 0.000" />
</filter>
</svg>
L'avantage est que nous n'avons plus besoin de stocker le fichier n'importe où, ni de le charger à partir du disque ou du réseau pour l'utiliser dans notre document HTML. Au lieu de faire référence au nom de fichier comme nous l'avons fait précédemment, nous pouvons désormais pointer vers l'URL des données:
<style>
:root {
filter: url('data:image/svg+xml,\
<svg xmlns="http://www.w3.org/2000/svg">\
<filter id="deuteranopia">\
<feColorMatrix values="0.367 0.861 -0.228 0.000 0.000\
0.280 0.673 0.047 0.000 0.000\
-0.012 0.043 0.969 0.000 0.000\
0.000 0.000 0.000 1.000 0.000" />\
</filter>\
</svg>#deuteranopia');
}
</style>
À la fin de l'URL, nous spécifions toujours l'ID du filtre que nous souhaitons utiliser, comme précédemment. Notez qu'il n'est pas nécessaire d'encoder le document SVG en Base64 dans l'URL. Cela ne ferait qu'altérer la lisibilité et augmenter la taille du fichier. Nous avons ajouté des barres obliques inverses à la fin de chaque ligne pour nous assurer que les caractères de nouvelle ligne de l'URL des données ne terminent pas la chaîne littérale CSS.
Jusqu'à présent, nous n'avons parlé que de la simulation de déficiences visuelles à l'aide de la technologie Web. Fait intéressant, notre implémentation finale dans le moteur de rendu Blink est en fait assez similaire. Voici un utilitaire d'assistance C++ que nous avons ajouté pour créer une URL de données avec une définition de filtre donnée, en fonction de la même technique:
AtomicString CreateFilterDataUrl(const char* piece) {
AtomicString url =
"data:image/svg+xml,"
"<svg xmlns=\"http://www.w3.org/2000/svg\">"
"<filter id=\"f\">" +
StringView(piece) +
"</filter>"
"</svg>"
"#f";
return url;
}
Et voici comment nous l'utilisons pour créer tous les filtres dont nous avons besoin:
AtomicString CreateVisionDeficiencyFilterUrl(VisionDeficiency vision_deficiency) {
switch (vision_deficiency) {
case VisionDeficiency::kAchromatopsia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kBlurredVision:
return CreateFilterDataUrl("<feGaussianBlur stdDeviation=\"2\"/>");
case VisionDeficiency::kDeuteranopia:
return CreateFilterDataUrl(
"<feColorMatrix values=\""
" 0.367 0.861 -0.228 0.000 0.000 "
" 0.280 0.673 0.047 0.000 0.000 "
"-0.012 0.043 0.969 0.000 0.000 "
" 0.000 0.000 0.000 1.000 0.000 "
"\"/>");
case VisionDeficiency::kProtanopia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kTritanopia:
return CreateFilterDataUrl("…");
case VisionDeficiency::kNoVisionDeficiency:
NOTREACHED();
return "";
}
}
Notez que cette technique nous donne accès à toute la puissance des filtres SVG sans avoir à réimplémenter quoi que ce soit ni à réinventer la roue. Nous implémentons une fonctionnalité de rendu Blink, mais nous le faisons en exploitant la plate-forme Web.
Nous avons donc découvert comment créer des filtres SVG et les transformer en URL de données que nous pouvons utiliser dans la valeur de la propriété CSS filter
. Pouvez-vous imaginer un problème avec cette technique ? En réalité, nous ne pouvons pas nous fier à l'URL des données qui est chargée dans tous les cas, car la page cible peut comporter un Content-Security-Policy
qui bloque les URL de données. Notre implémentation finale au niveau de Blink prend soin de contourner le CSP pour ces URL de données "internes" lors du chargement.
Mis à part les cas limites, nous avons bien progressé. Étant donné que nous ne dépendons plus de la présence d'<svg>
intégré dans le même document, nous avons réduit notre solution à une seule définition de propriété CSS filter
autonome. Parfait ! Supprimons-la également.
Éviter la dépendance CSS intégrée au document
Pour résumer, voici où nous en sommes:
<style>
:root {
filter: url('data:…');
}
</style>
Nous dépendons toujours de cette propriété CSS filter
, qui peut remplacer un filter
dans le document réel et causer des problèmes. Il s'afficherait également lors de l'inspection des styles calculés dans DevTools, ce qui serait source de confusion. Comment pouvons-nous éviter ces problèmes ? Nous devons trouver un moyen d'ajouter un filtre au document sans que les développeurs puissent l'observer de manière programmatique.
Une idée a été proposée : créer une propriété CSS interne à Chrome qui se comporte comme filter
, mais qui porte un nom différent, comme --internal-devtools-filter
. Nous pourrions ensuite ajouter une logique spéciale pour nous assurer que cette propriété ne s'affiche jamais dans DevTools ni dans les styles calculés du DOM. Nous pouvons même nous assurer qu'elle ne fonctionne que sur le seul élément dont nous avons besoin: l'élément racine. Toutefois, cette solution n'est pas idéale: nous dupliquerions une fonctionnalité qui existe déjà avec filter
. Même si nous nous efforçons de masquer cette propriété non standard, les développeurs Web pourraient quand même la découvrir et commencer à l'utiliser, ce qui serait mauvais pour la plate-forme Web. Nous avons besoin d'un autre moyen d'appliquer un style CSS sans qu'il ne soit observable dans le DOM. Auriez-vous quelqu'un en tête ?
La spécification CSS comporte une section présentant le modèle de mise en forme visuelle qu'elle utilise. L'un des concepts clés est le voile de fenêtre. Il s'agit de la vue visuelle à travers laquelle les utilisateurs consultent la page Web. Un concept étroitement lié est le bloc contenant initial, qui est un peu comme un viewport <div>
stylable qui n'existe qu'au niveau des spécifications. La spécification fait référence à ce concept de "fenêtre d'affichage" partout. Par exemple, savez-vous comment le navigateur affiche des barres de défilement lorsque le contenu ne tient pas dans l'écran ? Tout cela est défini dans la spécification CSS, en fonction de cette "fenêtre d'affichage".
Ce viewport
existe également dans le moteur de rendu Blink, en tant que détail d'implémentation. Voici le code qui applique les styles de vue par défaut conformément aux spécifications:
scoped_refptr<ComputedStyle> StyleResolver::StyleForViewport() {
scoped_refptr<ComputedStyle> viewport_style =
InitialStyleForElement(GetDocument());
viewport_style->SetZIndex(0);
viewport_style->SetIsStackingContextWithoutContainment(true);
viewport_style->SetDisplay(EDisplay::kBlock);
viewport_style->SetPosition(EPosition::kAbsolute);
viewport_style->SetOverflowX(EOverflow::kAuto);
viewport_style->SetOverflowY(EOverflow::kAuto);
// …
return viewport_style;
}
Vous n'avez pas besoin de comprendre C++ ni les subtilités du moteur de style de Blink pour voir que ce code gère les éléments z-index
, display
, position
et overflow
de la fenêtre d'affichage (ou, plus précisément, du bloc contenant le premier bloc). Ce sont tous des concepts que vous connaissez peut-être déjà en CSS. Il existe d'autres éléments magiques liés aux contextes d'empilement, qui ne se traduisent pas directement en propriété CSS. Toutefois, vous pouvez considérer cet objet viewport
comme quelque chose qui peut être stylisé à l'aide de CSS depuis Blink, comme un élément DOM, sauf qu'il ne fait pas partie du DOM.
Cela nous donne exactement ce que nous voulons ! Nous pouvons appliquer nos styles filter
à l'objet viewport
, ce qui affecte visuellement le rendu, sans interférer avec les styles de page observables ni le DOM.
Conclusion
Pour récapituler ce petit voyage, nous avons commencé par créer un prototype en utilisant la technologie Web au lieu de C++, puis nous avons commencé à travailler sur le déplacement de certaines parties de celui-ci vers le moteur de rendu Blink.
- Nous avons d'abord rendu notre prototype plus autonome en insérant des URL de données dans le code.
- Nous avons ensuite rendu ces URL de données internes compatibles avec le CSP en faisant une exception pour leur chargement.
- Nous avons rendu notre implémentation indépendante du DOM et non observable de manière programmatique en déplaçant les styles vers le
viewport
interne de Blink.
La particularité de cette implémentation est que notre prototype HTML/CSS/SVG a fini par influencer la conception technique finale. Nous avons trouvé un moyen d'utiliser la plate-forme Web, même dans le moteur de rendu Blink.
Pour en savoir plus, consultez notre proposition de conception ou le bug de suivi Chromium, qui fait référence à tous les correctifs associés.
Télécharger les canaux de prévisualisation
Vous pouvez utiliser la version Canary, Dev ou Bêta de Chrome comme navigateur de développement par défaut. Ces canaux de prévisualisation vous donnent accès aux dernières fonctionnalités de DevTools, vous permettent de tester les API de plates-formes Web de pointe et vous aident à détecter les problèmes sur votre site avant vos utilisateurs.
Contacter l'équipe des outils pour les développeurs Chrome
Utilisez les options suivantes pour discuter des nouvelles fonctionnalités, des mises à jour ou de tout autre élément lié aux outils pour les développeurs.
- Envoyez-nous vos commentaires et vos demandes de fonctionnalités sur crbug.com.
- Signalez un problème dans les outils de développement à l'aide de l'icône Plus d'options > Aide > Signaler un problème dans les outils de développement dans les outils de développement.
- Envoyez un tweet à @ChromeDevTools.
- Laissez des commentaires sur les vidéos YouTube sur les nouveautés dans les outils de développement ou sur les vidéos YouTube de conseils sur les outils de développement.