In diesem Artikel wird beschrieben, warum und wie wir eine Simulation einer Farbblindheit in den Entwicklertools und im Blink Renderer implementiert haben.
Hintergrund: schlechter Farbkontrast
Text mit geringem Kontrast ist das häufigste Problem bei der Barrierefreiheit im Internet, das automatisch erkannt werden kann.
Laut der WebAIM-Analyse der Barrierefreiheit der 1 Million beliebtesten Websites haben über 86% der Startseiten einen geringen Kontrast. Im Durchschnitt enthält jede Startseite 36 verschiedene Textelemente mit niedrigem Kontrast.
Mit den DevTools Kontrastprobleme finden, nachvollziehen und beheben
Die Chrome-Entwicklertools können Entwicklern und Designern helfen, den Kontrast zu verbessern und barrierefreiere Farbschemata für Webanwendungen auszuwählen:
- In der Kurzinfo zum Prüfmodus, die oben auf der Webseite angezeigt wird, wird das Kontrastverhältnis für Textelemente angezeigt.
- Die Farbauswahl in den DevTools zeigt schlechte Kontrastverhältnisse für Textelemente an, zeigt die empfohlene Kontrastlinie an, um die manuelle Auswahl besserer Farben zu erleichtern, und kann sogar barrierefreie Farben vorschlagen.
- Sowohl im Bereich „CSS-Übersicht“ als auch im Prüfbericht zur Lighthouse-Barrierefreiheit werden Textelemente mit geringem Kontrast auf Ihrer Seite aufgelistet.
Wir haben dieser Liste vor Kurzem ein neues Tool hinzugefügt, das sich ein wenig von den anderen unterscheidet. Die oben genannten Tools konzentrieren sich hauptsächlich auf die Anzeige von Informationen zum Kontrastverhältnis und bieten Optionen zur Korrektur. Wir haben festgestellt, dass Entwicklern in den DevTools noch keine Möglichkeit geboten wird, dieses Problemfeld besser zu verstehen. Deshalb haben wir auf dem Tab „Rendering“ in den DevTools eine Simulation von Sehschwäche implementiert.
In Puppeteer können Sie diese Simulationen mithilfe der neuen page.emulateVisionDeficiency(type)
API programmatisch aktivieren.
Farbsehschwäche
Etwa 1 von 20 Menschen leidet an einer Farbsehschwäche (auch als „Farbenblindheit“ bezeichnet). Solche Beeinträchtigungen erschweren es, verschiedene Farben zu unterscheiden, was Kontrastprobleme verstärken kann.
Als Entwickler mit normalem Sehvermögen sehen Sie in den DevTools möglicherweise ein schlechtes Kontrastverhältnis für Farbpaare, die für Sie optisch in Ordnung sind. Das liegt daran, dass die Formeln für das Kontrastverhältnis diese Farbfehlsichtigkeiten berücksichtigen. Sie können in einigen Fällen möglicherweise noch Text mit geringem Kontrast lesen, aber Menschen mit Sehbehinderung haben dieses Privileg nicht.
Mit der Möglichkeit, die Auswirkungen dieser Sehbeeinträchtigungen in ihren eigenen Web-Apps zu simulieren, möchten wir das fehlende Puzzleteil liefern: Mit DevTools können Sie nicht nur Kontrastprobleme finden und beheben, sondern jetzt auch verstehen.
Simulieren von Farbblindheitsschwächen mit HTML, CSS, SVG und C++
Bevor wir uns mit der Implementierung unserer Funktion im Blink-Renderer befassen, ist es hilfreich zu verstehen, wie Sie eine entsprechende Funktion mithilfe von Webtechnologien implementieren würden.
Stellen Sie sich jede dieser Simulationen einer Farbblindheit als Overlay vor, das die gesamte Seite abdeckt. Die Webplattform bietet eine Möglichkeit dazu: CSS-Filter. Mit der CSS-Eigenschaft filter
können Sie einige vordefinierte Filterfunktionen wie blur
, contrast
, grayscale
und hue-rotate
verwenden. Für noch mehr Kontrolle akzeptiert die Eigenschaft filter
auch eine URL, die auf eine benutzerdefinierte SVG-Filterdefinition verweisen kann:
<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>
Im obigen Beispiel wird eine benutzerdefinierte Filterdefinition verwendet, die auf einer Farbmatrix basiert. Der [Red, Green, Blue, Alpha]
-Farbwert jedes Pixels wird konzeptionell mit einer Matrix multipliziert, um eine neue Farbe [R′, G′, B′, A′]
zu erzeugen.
Jede Zeile in der Matrix enthält 5 Werte: einen Multiplikator für R, G, B und A (von links nach rechts) sowie einen fünften Wert für einen konstanten Verschiebungswert. Es gibt 4 Zeilen: Die erste Zeile der Matrix wird verwendet, um den neuen Rotwert zu berechnen, die zweite Zeile Grün, die dritte Zeile Blau und die letzte Zeile Alpha.
Sie fragen sich vielleicht, woher die genauen Zahlen in unserem Beispiel stammen. Was macht diese Farbmatrix zu einer guten Annäherung an Deuteranopie? Die Antwort lautet: Wissenschaft! Die Werte basieren auf einem physiologisch korrekten Simulationsmodell für Farbfehlsichtigkeit von Machado, Oliveira und Fernandes.
Wie auch immer, wir haben diesen SVG-Filter, den wir jetzt mithilfe von CSS auf beliebige Elemente auf der Seite anwenden können. Das gleiche Muster lässt sich auch auf andere Sehbeeinträchtigungen anwenden. Hier ist eine Demo, wie das aussieht:
Wir könnten unsere DevTools-Funktion so implementieren: Wenn der Nutzer in der DevTools-Benutzeroberfläche eine Sehschwäche emuliert, wird der SVG-Filter in das geprüfte Dokument eingefügt und dann wird der Filterstil auf das Stammelement angewendet. Dieser Ansatz hat jedoch mehrere Probleme:
- Die Seite hat möglicherweise bereits einen Filter auf dem Stammelement, der dann von unserem Code überschrieben wird.
- Auf der Seite befindet sich möglicherweise bereits ein Element mit
id="deuteranopia"
, das mit unserer Filterdefinition kollidiert. - Die Seite basiert möglicherweise auf einer bestimmten DOM-Struktur und durch das Einfügen des
<svg>
in das DOM werden diese Annahmen möglicherweise verletzt.
Abgesehen von Grenzfällen besteht das Hauptproblem bei diesem Ansatz darin, dass wir programmatisch beobachtbare Änderungen an der Seite vornehmen würden. Wenn ein DevTools-Nutzer das DOM untersucht, sieht er möglicherweise plötzlich ein <svg>
-Element, das er nie hinzugefügt hat, oder einen CSS-filter
, den er nie geschrieben hat. Das wäre verwirrend! Um diese Funktion in den Entwicklertools zu implementieren, benötigen wir eine Lösung, die diese Nachteile nicht hat.
Sehen wir uns an, wie wir das weniger aufdringlich gestalten können. Bei dieser Lösung müssen wir zwei Teile ausblenden: 1) den CSS-Stil mit der filter
-Property und 2) die SVG-Filterdefinition, die derzeit Teil des DOM ist.
<!-- 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>
SVG-Abhängigkeit im Dokument vermeiden
Beginnen wir mit Teil 2: Wie können wir vermeiden, das SVG dem DOM hinzuzufügen? Eine Möglichkeit besteht darin, sie in eine separate SVG-Datei zu verschieben. Wir können die <svg>…</svg>
aus dem obigen HTML-Code kopieren und als filter.svg
speichern. Dazu müssen wir aber zuerst einige Änderungen vornehmen. Inline-SVG in HTML folgt den HTML-Parseregeln. Das bedeutet, dass Sie in einigen Fällen Attributwerte auch ohne Anführungszeichen eingeben können. SVG-Dateien in separaten Dateien müssen jedoch gültige XML-Dateien sein und das Parsen von XML ist wesentlich strenger als das HTML. Hier ist noch einmal unser SVG-in-HTML-Snippet:
<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>
Damit dies ein gültiges eigenständiges SVG (und damit XML) ist, müssen wir einige Änderungen vornehmen. Können Sie erraten, welche?
<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>
Die erste Änderung betrifft die XML-Namespacedeklaration oben. Die zweite Ergänzung ist der sogenannte Schrägstrich, der angibt, dass das <feColorMatrix>
-Tag das Element sowohl öffnet als auch schließt. Diese letzte Änderung ist eigentlich nicht erforderlich. Wir könnten stattdessen einfach das explizite </feColorMatrix>
-Schluss-Tag verwenden. Da aber sowohl XML als auch SVG-in-HTML diese />
-Kurzschreibweise unterstützen, können wir sie auch verwenden.
Mit diesen Änderungen können wir die Datei endlich als gültige SVG-Datei speichern und im HTML-Dokument über den CSS-Attributwert filter
darauf verweisen:
<style>
:root {
filter: url(filters.svg#deuteranopia);
}
</style>
Hurra, wir müssen SVG nicht mehr in das Dokument einfügen! Das ist schon viel besser. Aber… wir benötigen jetzt eine separate Datei. Das ist immer noch eine Abhängigkeit. Können wir es irgendwie loswerden?
Wie sich herausstellt, benötigen wir eigentlich keine Datei. Wir können die gesamte Datei in einer URL codieren, indem wir eine Daten-URL verwenden. Dazu nehmen wir den Inhalt der SVG-Datei, fügen das Präfix data:
hinzu und konfigurieren den richtigen MIME-Typ. So erhalten wir eine gültige Daten-URL, die dieselbe SVG-Datei darstellt:
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>
Der Vorteil ist, dass wir die Datei jetzt nicht mehr irgendwo speichern oder von einer Festplatte oder über das Netzwerk laden müssen, nur um sie in unserem HTML-Dokument zu verwenden. Anstatt wie zuvor auf den Dateinamen zu verweisen, können wir jetzt auf die Daten-URL verweisen:
<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>
Am Ende der URL geben wir wie zuvor die ID des gewünschten Filters an. Das SVG-Dokument muss nicht Base64-codiert in die URL eingefügt werden. Das würde nur die Lesbarkeit beeinträchtigen und die Dateigröße erhöhen. Wir haben am Ende jeder Zeile Backslashes hinzugefügt, damit die Zeilenumbruchzeichen in der Daten-URL das CSS-Stringliteral nicht beenden.
Bisher haben wir nur darüber gesprochen, wie Sehschwächen mithilfe von Webtechnologien simuliert werden können. Interessanterweise ist unsere endgültige Implementierung im Blink-Renderer ziemlich ähnlich. Hier ist ein C++-Hilfsprogramm, das wir hinzugefügt haben, um eine Daten-URL mit einer bestimmten Filterdefinition zu erstellen. Es basiert auf derselben Methode:
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;
}
Und so verwenden wir es, um alle erforderlichen Filter zu erstellen:
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 "";
}
}
Mit dieser Technik haben wir Zugriff auf die volle Leistung von SVG-Filtern, ohne etwas neu implementieren oder neu erfinden zu müssen. Wir implementieren eine Blink-Renderer-Funktion, nutzen dabei aber die Webplattform.
Wir haben also herausgefunden, wie wir SVG-Filter erstellen und in Daten-URLs umwandeln, die wir im Wert der CSS-Property filter
verwenden können. Können Sie sich ein Problem mit dieser Technik vorstellen? Es hat sich herausgestellt, dass wir uns nicht in jedem Fall darauf verlassen können, dass die Daten-URL geladen wird, da die Landingpage möglicherweise eine Content-Security-Policy
hat, die Daten-URLs blockiert. Bei der endgültigen Implementierung auf Blinkebene wird besonders darauf geachtet, die CSP für diese „internen“ Daten-URLs beim Laden zu umgehen.
Abgesehen von Grenzfällen haben wir gute Fortschritte gemacht. Da wir nicht mehr darauf angewiesen sind, dass Inline-<svg>
im selben Dokument vorhanden ist, haben wir unsere Lösung auf eine einzige eigenständige CSS-filter
-Property-Definition reduziert. Sehr gut! Jetzt beseitigen wir auch das.
CSS-Abhängigkeit im Dokument vermeiden
Noch einmal kurz zur Erinnerung:
<style>
:root {
filter: url('data:…');
}
</style>
Wir sind immer noch auf diese CSS-Eigenschaft filter
angewiesen, die eine filter
im tatsächlichen Dokument überschreiben und zu Fehlern führen kann. Außerdem würde es beim Prüfen der berechneten Stile in den Entwicklertools angezeigt werden, was verwirrend wäre. Wie können wir diese Probleme vermeiden? Wir müssen eine Möglichkeit finden, dem Dokument einen Filter hinzuzufügen, ohne dass er für Entwickler programmatisch beobachtet werden kann.
Eine Idee war, eine neue interne CSS-Property für Chrome zu erstellen, die sich wie filter
verhält, aber einen anderen Namen hat, z. B. --internal-devtools-filter
. Wir könnten dann eine spezielle Logik hinzufügen, damit diese Eigenschaft nie in den DevTools oder in den berechneten Stilen im DOM angezeigt wird. Wir könnten sogar sicherstellen, dass es nur für das eine Element funktioniert, für das wir es benötigen: das Stammelement. Diese Lösung wäre jedoch nicht ideal: Wir würden Funktionen duplizieren, die bereits mit filter
vorhanden sind. Und selbst wenn wir uns bemühen würden, diese nicht standardmäßige Eigenschaft zu verstecken, könnten Webentwickler sie trotzdem finden und verwenden, was für die Webplattform schädlich wäre. Wir brauchen eine andere Möglichkeit, einen CSS-Stil anzuwenden, ohne dass er im DOM sichtbar ist. Könnten Sie mir weiterhelfen?
Die CSS-Spezifikation enthält einen Abschnitt, in dem das verwendete visuelle Formatierungsmodell vorgestellt wird. Eines der wichtigsten Konzepte dort ist der Bildschirmbereich. Das ist die visuelle Ansicht, über die Nutzer die Webseite aufrufen. Ein eng verwandtes Konzept ist der initiale enthaltende Block. Er ist eine Art stilisierbarer Viewport <div>
, der nur auf Spezifikationsebene existiert. In der Spezifikation wird dieses Konzept des „Darstellungsbereichs“ immer wieder erwähnt. Wissen Sie beispielsweise, wie der Browser Bildlaufleisten anzeigt, wenn der Inhalt nicht passt? All dies ist in der CSS-Spezifikation basierend auf diesem „Darstellungsbereich“ definiert.
Diese viewport
gibt es auch im Blink-Renderer als Implementierungsdetail. Hier ist der Code, mit dem die Standard-Darstellungsstilen gemäß der Spezifikation angewendet werden:
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;
}
Sie müssen weder C++ noch die Feinheiten der Blink-Style-Engine verstehen, um zu erkennen, dass dieser Code z-index
, display
, position
und overflow
des Viewports (oder genauer: des ursprünglichen enthaltenden Blocks) verarbeitet. Das sind alles Konzepte, mit denen Sie wahrscheinlich schon von CSS vertraut sind. Es gibt noch weitere magische Dinge im Zusammenhang mit Stapelkontexten, die sich nicht direkt in eine CSS-Property umwandeln lassen. Insgesamt können Sie sich dieses viewport
-Objekt als etwas vorstellen, das wie ein DOM-Element mit CSS innerhalb von Blink gestaltet werden kann – mit der Ausnahme, dass es nicht Teil des DOM ist.
Das ist genau das, was wir wollen! Wir können unsere filter
-Stile auf das viewport
-Objekt anwenden, was das Rendering visuell beeinflusst, ohne die beobachtbaren Seitenstile oder das DOM zu beeinträchtigen.
Fazit
Um unsere kleine Reise hier zusammenzufassen: Wir haben mit dem Erstellen eines Prototyps mit Webtechnologie anstelle von C++ begonnen und dann damit begonnen, Teile davon in den Blink-Renderer zu verschieben.
- Wir haben unseren Prototyp zunächst durch Inlinen von Daten-URLs unabhängiger gemacht.
- Anschließend haben wir diese internen Daten-URLs CSP-freundlich gemacht, indem wir das Laden speziell behandelt haben.
- Wir haben unsere Implementierung DOM-unabhängig und programmatisch nicht beobachtbar gemacht, indem wir Stile in die interne
viewport
von Blink verschoben haben.
Das Besondere an dieser Implementierung ist, dass unser HTML-/CSS-/SVG-Prototyp das endgültige technische Design beeinflusst hat. Wir haben eine Möglichkeit gefunden, die Webplattform zu verwenden, sogar innerhalb des Blink Renderer!
Weitere Informationen finden Sie in unserem Designvorschlag oder im Chromium-Tracking-Fehler, der alle zugehörigen Patches enthält.
Vorschaukanäle herunterladen
Sie können Canary, Dev oder Beta als Standardbrowser für die Entwicklung verwenden. Diese Vorabversionen bieten Zugriff auf die neuesten DevTools-Funktionen, ermöglichen den Test moderner Webplattform-APIs und helfen Ihnen, Probleme auf Ihrer Website zu finden, bevor Ihre Nutzer sie bemerken.
Chrome-Entwicklertools-Team kontaktieren
Mit den folgenden Optionen können Sie über neue Funktionen, Updates oder andere Themen im Zusammenhang mit den DevTools sprechen.
- Senden Sie uns Feedback und Funktionsanfragen unter crbug.com.
- Melden Sie ein DevTools-Problem über das Dreipunkt-Menü Weitere Optionen > Hilfe > DevTools-Problem melden.
- Tweeten Sie an @ChromeDevTools.
- Hinterlassen Sie Kommentare unter den YouTube-Videos zu den Neuigkeiten in den DevTools oder den YouTube-Videos mit Tipps zu den DevTools.