Simulieren von Farbsehschwächen in Blink Renderer

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.

Eine Liste häufiger Probleme mit der Barrierefreiheit im Internet. Text mit geringem Kontrast ist bei Weitem das häufigste Problem.

Laut einer Analyse der von WebAIM durchgeführten Barrierefreiheit im Internet der beliebtesten 1 Million Websites weisen über 86% der Startseiten einen geringen Kontrast auf. Im Durchschnitt gibt es auf jeder Startseite 36 verschiedene Instanzen von Text mit niedrigem Kontrast.

Kontrastprobleme mit den Entwicklertools finden, verstehen und beheben

Mit den Chrome-Entwicklertools können Entwickler und Designer den Kontrast verbessern und barrierefreiere Farbschemas für Webanwendungen auswählen:

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. Uns wurde klar, dass es mit den Entwicklertools fehlte eine Möglichkeit für Entwickler, ein tieferes Verständnis dieses Problembereichs zu finden. Deshalb haben wir auf dem Tab „Rendering“ in den Entwicklertools eine Simulation einer Sehschwäche implementiert.

In Puppeteer können Sie diese Simulationen mit der neuen page.emulateVisionDeficiency(type) API programmatisch aktivieren.

Farbblindheit

Etwa 1 von 20 Personen leidet an einer Farbblindheit (auch als der weniger genaue Begriff „Farbenblindheit“ bezeichnet). Solche Beeinträchtigungen erschweren es, verschiedene Farben auseinanderzuhalten, was Kontrastprobleme verstärken kann.

<ph type="x-smartling-placeholder">
</ph> Ein farbenprächtiges Bild von geschmolzenen Buntstiften ohne simulierte Farbblindheit <ph type="x-smartling-placeholder">
</ph> Ein farbenprächtiges Bild geschmolzener Buntstifte, bei dem keine Farbblindheit simuliert wird.
<ph type="x-smartling-placeholder">
</ph> ALT_TEXT_HERE
Die Auswirkung der Simulation von Achromatopsie auf ein farbenfrohes Bild geschmolzener Buntstifte.
<ph type="x-smartling-placeholder">
</ph> Die Auswirkungen der Simulation von Deuteranopie auf ein farbenfrohes Bild geschmolzener Buntstifte.
Die Auswirkungen der Simulation von Deuteranopie auf ein farbenfrohes Bild geschmolzener Buntstifte.
<ph type="x-smartling-placeholder">
</ph> Die Auswirkungen der Simulation der Protanopie auf ein farbenfrohes Bild geschmolzener Buntstifte.
Die Auswirkungen der Simulation der Protanopie auf ein farbenfrohes Bild geschmolzener Buntstifte.
<ph type="x-smartling-placeholder">
</ph> Die Auswirkung der Simulation von Tritanopie auf ein farbenfrohes Bild geschmolzener Buntstifte.
Die Auswirkung der Simulation von Tritanopie auf ein farbenfrohes Bild geschmolzener Buntstifte.

Als Entwickler mit normalem Sehvermögen sehen Sie möglicherweise, dass die Entwicklertools ein schlechtes Kontrastverhältnis für Farbpaare aufweisen, die für Sie in Ordnung sind. Dies liegt daran, dass die Formeln für das Kontrastverhältnis diese Farbblindheit miteinbeziehen! Sie können in einigen Fällen zwar Text mit geringem Kontrast lesen, aber Personen mit Sehbehinderung haben diese Berechtigung nicht.

Da Designer und Entwickler die Auswirkungen dieser Sehschwächen auf ihre eigenen Web-Apps simulieren können, möchten wir mit den Entwicklertools nicht nur Kontrastprobleme finden und beheben, sondern sie auch verstehen.

Simulieren von Farbblindheitsschwächen mit HTML, CSS, SVG und C++

Bevor wir uns mit der Blink Renderer-Implementierung unserer Funktion befassen, ist es wichtig zu verstehen, wie Sie eine gleichwertige Funktionalität mit Webtechnologie implementieren.

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 dem CSS-Attribut filter können Sie vordefinierte Filterfunktionen verwenden, z. B. blur, contrast, grayscale und hue-rotate. 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. Prinzipiell wird der [Red, Green, Blue, Alpha]-Farbwert jedes Pixels mit einer Matrixmultiplikation versehen, um eine neue Farbe [R′, G′, B′, A′] zu erstellen.

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. Warum ist diese Farbmatrix eine gute Annäherung an die Deuteranopie? Die Antwort lautet: Wissenschaft! Die Werte basieren auf einem physiologisch genauen Simulationsmodell für eine Farbblindheit 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. Dasselbe Muster können wir für andere Sehschwächen wiederholen. Hier ist eine Demo, wie das aussieht:

Wenn wir möchten, könnten wir unsere Entwicklertools-Funktion wie folgt erstellen: Wenn der Nutzer in der Entwicklertools-Benutzeroberfläche eine Sehschwäche emuliert, fügen wir den SVG-Filter in das geprüfte Dokument ein und wenden dann den Filterstil auf das Stammelement an. Bei diesem Ansatz gibt es jedoch einige Probleme:

  • Möglicherweise verfügt die Seite bereits über einen Filter für ihr Stammelement, den unser Code dann überschreiben könnte.
  • Möglicherweise enthält die Seite bereits ein Element mit id="deuteranopia", das mit unserer Filterdefinition in Konflikt steht.
  • Die Seite basiert möglicherweise auf einer bestimmten DOM-Struktur. Wenn wir <svg> in das DOM einfügen, verstoßen wir möglicherweise gegen diese Annahmen.

Abgesehen von Grenzfällen besteht das Hauptproblem bei diesem Ansatz darin, dass wir programmatisch beobachtbare Änderungen an der Seite vornehmen würden. Wenn ein Entwicklertools-Nutzer das DOM überprüft, sieht er möglicherweise plötzlich ein <svg>-Element, das er nie hinzugefügt hat, oder ein CSS-filter, das 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 dies weniger aufdringlich machen können. Diese Lösung enthält zwei Teile, die ausgeblendet werden müssen: 1) den CSS-Stil mit der Eigenschaft filter und 2) die SVG-Filterdefinition, die derzeit Teil des DOMs 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 verhindern, dass die SVG-Datei dem DOM hinzugefügt wird? Eine Idee ist, sie in eine separate SVG-Datei zu verschieben. Wir können die <svg>…</svg> aus dem HTML-Code oben kopieren und als filter.svg speichern – aber wir müssen zuerst einige Änderungen vornehmen. Inline-SVG in HTML folgt den HTML-Parsing-Regeln. Das bedeutet, dass Sie beispielsweise in einigen Fällen Anführungszeichen um Attributwerte weglassen können. SVG-Dateien in separaten Dateien gelten jedoch als gültiger XML-Code. Das Parsen von XML ist wesentlich strenger als das von HTML. Hier 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>

Um diese gültige eigenständige SVG-Datei (und damit XML) zu erhalten, müssen wir einige Änderungen vornehmen. Kannst du ihn erraten?

<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 ist die Deklaration des XML-Namespace oben. Die zweite Hinzufügung ist der sogenannte „Schrägstrich“ – der Schrägstrich, der angibt, dass das <feColorMatrix>-Tag das Element öffnet und schließt. Diese letzte Änderung ist eigentlich nicht notwendig. Wir könnten stattdessen einfach das explizite schließende </feColorMatrix>-Tag beibehalten, aber da sowohl XML als auch SVG-in-HTML diese />-Kürzel unterstützen, könnten wir sie auch verwenden.

Wie auch immer, mit diesen Änderungen können wir die Datei endlich als gültige SVG-Datei speichern und vom CSS-Eigenschaftswert filter in unserem HTML-Dokument 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 sind jetzt auf eine separate Datei angewiesen. 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 innerhalb einer URL mithilfe einer Daten-URL codieren. Dazu nehmen wir buchstäblich den Inhalt der vorherigen SVG-Datei, fügen das Präfix data: hinzu, konfigurieren den richtigen MIME-Typ und erhalten 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 also 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 noch wie zuvor die ID des zu verwendenden Filters an. Beachten Sie, dass das SVG-Dokument in der URL nicht mit Base64 codiert werden muss. Dies würde nur die Lesbarkeit beeinträchtigen und die Dateigröße erhöhen. Wir haben umgekehrte Schrägstriche am Ende jeder Zeile hinzugefügt, um sicherzustellen, dass 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 sieht unsere endgültige Implementierung im Blink Renderer eigentlich ziemlich ähnlich aus. Hier ist ein C++-Hilfsprogramm, das wir hinzugefügt haben, um eine Daten-URL mit einer bestimmten Filterdefinition mit derselben Technik zu erstellen:

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 können wir die volle Leistung von SVG-Filtern nutzen, ohne etwas neu implementieren oder Räder neu erfinden zu müssen. Wir implementieren eine Blink Renderer-Funktion, nutzen dafür aber die Webplattform.

Jetzt wissen wir, wie wir SVG-Filter erstellen und in Daten-URLs umwandeln können, die wir in unserem CSS-Eigenschaftswert filter verwenden können. Fällt Ihnen ein Problem mit dieser Technik ein? Es hat sich herausgestellt, dass wir uns nicht in jedem Fall auf die Daten-URL verlassen können, die geladen wird, da die Landingpage möglicherweise ein Content-Security-Policy hat, das Daten-URLs blockiert. Bei der endgültigen Implementierung auf Blinkebene wird insbesondere die CSP für diese „internen“ Daten-URLs beim Laden umgangen.

Abgesehen von den Grenzfällen haben wir gute Fortschritte gemacht. Da wir uns nicht mehr darauf verlassen müssen, dass Inline-<svg> im selben Dokument vorhanden ist, haben wir unsere Lösung effektiv auf eine einzige eigenständige CSS-filter-Eigenschaftsdefinition reduziert. Sehr gut! Das wollen wir auch weg.

CSS-Abhängigkeit im Dokument vermeiden

Noch einmal kurz zur Erinnerung:

<style>
  :root {
    filter: url('data:…');
  }
</style>

Wir sind weiterhin auf diese CSS-filter-Eigenschaft angewiesen, die ein filter im echten Dokument überschreiben und Probleme verursachen kann. Er würde auch angezeigt, wenn Sie die berechneten Stile in den Entwicklertools überprüfen, 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.

So wurde beispielsweise eine neue Chrome-interne CSS-Property erstellt, die sich ähnlich wie filter verhält, aber einen anderen Namen hat, z. B. --internal-devtools-filter. Wir könnten dann eine spezielle Logik hinzufügen, um sicherzustellen, dass diese Eigenschaft nie in den Entwicklertools 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 bereits mit filter vorhandene Funktionen duplizieren, und selbst wenn wir versuchen, diese nicht standardmäßige Property zu verbergen, könnten Webentwickler trotzdem darüber herausfinden und beginnen, sie zu verwenden, was für die Webplattform schlecht wäre. Wir benötigen eine andere Methode, um einen CSS-Stil anzuwenden, ohne dass er im DOM beobachtbar ist. Haben Sie Ideen dazu?

Die CSS-Spezifikation enthält einen Abschnitt, in dem das verwendete visuelle Formatierungsmodell vorgestellt wird. Eines der wichtigsten Konzepte ist der Darstellungsbereich. Dies ist die visuelle Ansicht, über die Nutzende die Webseite konsultieren. Ein eng verwandtes Konzept ist der anfängliche enthaltende Block, der einem anpassbaren Darstellungsbereich <div> ähnelt, der nur auf Spezifikationsebene vorhanden ist. Die Spezifikation bezieht sich überall auf dieses Konzept des Darstellungsbereichs. Wissen Sie zum Beispiel, wie der Browser Bildlaufleisten anzeigt, wenn der Inhalt nicht passt? All dies ist basierend auf diesem „Darstellungsbereich“ in der CSS-Spezifikation definiert.

Dieses viewport-Element ist im Blink-Renderer auch als Implementierungsdetail vorhanden. Hier ist der Code, mit dem die Standardstile für den Darstellungsbereich 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 sich nicht mit C++ oder den Feinheiten der Style-Engine von Blink vertraut machen, um zu sehen, ob dieser Code die z-index, display, position und overflow des Darstellungsbereichs (oder genauer gesagt, des ersten enthaltenden Blocks) verarbeitet. Das sind alles Konzepte, mit denen Sie wahrscheinlich von CSS vertraut sind. Es gibt noch eine weitere Magie beim Stapeln von Kontexten, die sich nicht direkt in eine CSS-Eigenschaft übertragen lassen. Insgesamt können Sie sich dieses viewport-Objekt jedoch als etwas vorstellen, das mit CSS aus Blink heraus gestaltet werden kann, genau wie ein DOM-Element – außer es ist nicht Teil des DOMs.

So haben wir 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

Zur Erinnerung: Wir haben zunächst mit Webtechnologie anstelle von C++ einen Prototyp erstellt 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 ihre Ladevorgänge in Groß- und Kleinschreibung geändert haben.
  • Wir haben unsere Implementierung DOM-unabhängig und programmatisch nicht beobachtbar gemacht, indem wir Stile in die blink-interne viewport verschoben haben.

Das Besondere an dieser Implementierung ist, dass unser HTML/CSS/SVG-Prototyp letztendlich das endgültige technische Design beeinflusst hat. Wir haben eine Möglichkeit gefunden, die Webplattform zu verwenden, sogar innerhalb des Blink Renderer!

Weitere Hintergrundinformationen finden Sie in unserem Designvorschlag oder im Chromium-Tracking-Fehler, in dem alle zugehörigen Patches beschrieben werden.

Vorschaukanäle herunterladen

Sie können Canary, Dev oder Beta als Standardbrowser für die Entwicklung verwenden. Über diese Vorschaukanäle erhalten Sie Zugriff auf die neuesten Entwicklertools, können innovative Webplattform-APIs testen und Probleme auf Ihrer Website erkennen, bevor Ihre Nutzer es tun.

Kontaktaufnahme mit dem Team für Chrome-Entwicklertools

Mit den folgenden Optionen kannst du die neuen Funktionen und Änderungen des Beitrags oder andere Aspekte der Entwicklertools besprechen.

  • Senden Sie uns über crbug.com einen Vorschlag oder Feedback.
  • Problem mit den Entwicklertools über Weitere Optionen melden Mehr > Hilfe > Hier kannst du Probleme mit den Entwicklertools in den Entwicklertools melden.
  • Twittern Sie unter @ChromeDevTools.
  • Hinterlasse Kommentare in den YouTube-Videos mit den Neuerungen in den Entwicklertools oder in YouTube-Videos mit Tipps zu den Entwicklertools.