Symulowanie niedoskonałości widzenia kolorów w narzędziu renderowania Blink Renderer

Z tego artykułu dowiesz się, dlaczego i jak wdrożyliśmy symulację niedoboru rozpoznawania barw w Narzędziach deweloperskich i w narzędziu Blink Renderer.

Tło: zły kontrast kolorów

Tekst o niskim kontraście to najczęstszy automatycznie wykrywalny problem z ułatwieniami dostępu w internecie.

Lista typowych problemów z ułatwieniami dostępu w internecie. Najczęstszym problemem jest tekst o niskim kontraście.

Według przeprowadzonej przez WebAIM analizy ułatwień dostępu obejmującej milion najpopularniejszych witryn ponad 86% stron głównych cechuje niski kontrast. Średnio na każdej stronie głównej znajduje się 36 wystąpień tekstu o niskim kontraście.

Wyszukiwanie, analizowanie i rozwiązywanie problemów z kontrastem za pomocą Narzędzi deweloperskich

Narzędzia deweloperskie w Chrome mogą pomóc deweloperom i projektantom poprawić kontrast i wybrać bardziej przystępne schematy kolorów w aplikacjach internetowych:

Niedawno dodaliśmy do tej listy nowe narzędzie, które różni się od innych. Opisane wyżej narzędzia koncentrują się na wyświetlaniu informacji o współczynniku kontrastu i umożliwiają poprawienie tych informacji. Zdaliśmy sobie sprawę, że Narzędzia deweloperskie wciąż nie pozwalają deweloperom na lepsze zrozumienie tej kwestii. Aby rozwiązać ten problem, na karcie Renderowanie w Narzędziach deweloperskich wdrożyliśmy symulację niedowidzących.

W Puppeteer nowy interfejs API page.emulateVisionDeficiency(type) umożliwia automatyczne włączanie tych symulacji.

Zaburzenia rozpoznawania barw

Około 1 na 20 osób cierpi na zaburzenie rozpoznawania barw (nazywane też mniej precyzyjnym terminem „daltonizm”). Utrudni to rozróżnianie kolorów, co może pogłębiać problemy z kontrastem.

Kolorowy obraz roztopionych kredek bez symulowanych wad wzroku
Kolorowe obraz roztopionych kredek bez symulowanych wad wzroku.
ALT_TEXT_HERE
Wpływ symulowanej achromatopsji na kolorowy obraz roztopionych kredek.
Wpływ symulowanej deuteranoii na kolorowy obraz roztopionych kredek.
Wpływ symulowanej deuteranoii na kolorowy obraz roztopionych kredek.
Wpływ symulowania protanopii na kolorowy obraz roztopionych kredek.
Wpływ symulowania protanopii na kolorowy obraz roztopionych kredek.
Wpływ symulowania tritanopii na kolorowy obraz roztopionych kredek.
Wpływ symulowania tritanopii na kolorowy obraz roztopionych kredek.

Jako deweloper z normalnym wzrokiem możesz zauważyć, że Narzędzia deweloperskie wyświetlają niski współczynnik kontrastu dla par kolorów, które wydają Ci się odpowiednie. Dzieje się tak, ponieważ formuły dotyczące współczynnika kontrastu uwzględniają te niedobory rozpoznawania kolorów. W niektórych przypadkach możesz czytać tekst o niskim kontraście, ale osoby niedowidzące nie mają takich uprawnień.

Pozwalając projektantom i deweloperom symulować wpływ tych zaburzeń wzroku na ich własne aplikacje internetowe, chcemy zapewnić im brakujący element systemu – Narzędzia deweloperskie nie tylko pomagają znaleźć i naprawić problemy z kontrastem, ale także je zrozumieć.

Symulowanie niedoborów rozpoznawania barw za pomocą kodu HTML, CSS, SVG i C++

Zanim przejdziemy do implementacji mechanizmu renderowania Blink, warto dowiedzieć się, jak za pomocą technologii internetowej można wdrożyć równoważne funkcje.

Każdą z tych symulacji zaburzeń rozpoznawania barw można traktować jak nakładkę zakrywającą całą stronę. Umożliwia to platforma internetowa: filtry CSS. Dzięki właściwości filter CSS możesz używać niektórych wstępnie zdefiniowanych funkcji filtrów, takich jak blur, contrast, grayscale, hue-rotate. Aby zapewnić jeszcze większą kontrolę, właściwość filter akceptuje też adresy URL, które mogą wskazywać na niestandardową definicję filtra SVG:

<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>

W przykładzie powyżej użyto niestandardowej definicji filtra opartej na matrycy kolorów. Zgodnie z zasadą wartość koloru [Red, Green, Blue, Alpha] każdego piksela jest mnożona przez macierz, aby utworzyć nowy kolor [R′, G′, B′, A′].

Każdy wiersz macierzy zawiera 5 wartości: mnożnik dla (od lewej do prawej) R, G, B i A, a także piąta wartość stałego przesunięcia. Są 4 wiersze: pierwszy wiersz macierzy służy do obliczania nowej wartości koloru czerwonego, drugi wiersz zielony, trzeci wiersz niebieski, a ostatni wiersz – alfa.

Być może zastanawiasz się, skąd pochodzą dokładne liczby podane w naszym przykładzie. Co sprawia, że ta matryca kolorów dobrze odzwierciedla deuteranopię? Odpowiedź brzmi: nauka. Wartości są oparte na dokładnym fizjologicznie symulującym niedoboru wzroku opracowanym przez Machado, Oliveirę i Franandesa.

W każdym razie mamy filtr SVG, który teraz możemy zastosować do dowolnych elementów na stronie za pomocą CSS. Możemy powtórzyć ten sam wzorzec dla innych wad wzroku. Oto przykład, jak to będzie wyglądać:

Możemy utworzyć funkcję w Narzędziach deweloperskich w ten sposób: gdy użytkownik emuluje niedowidzenie w interfejsie Narzędzi deweloperskich, wstrzykujemy filtr SVG do sprawdzanego dokumentu, a następnie stosujemy styl filtra do elementu głównego. Takie podejście wiąże się jednak z pewnymi problemami:

  • Strona może mieć już filtr w elemencie głównym, który w takiej sytuacji może zostać zastąpiony przez nasz kod.
  • Strona może już zawierać element z atrybutem id="deuteranopia", co jest niezgodne z definicją filtra.
  • Strona może opierać się na określonej strukturze DOM. Wstawienie do DOM elementu <svg> może spowodować naruszenie tych założeń.

Poza rzadkimi przypadkami najpoważniejszym problemem w przypadku tego podejścia jest to, że będziemy wprowadzać programowo obserwowalne zmiany na stronie. Jeśli użytkownik Narzędzi deweloperskich sprawdzi DOM, może nagle zobaczyć element <svg>, którego nigdy nie dodał, lub element CSS filter, którego nigdy nie zapisał. To byłoby mylące. Aby wdrożyć tę funkcję w Narzędziach deweloperskich, potrzebujemy rozwiązania, które nie ma tych wad.

Zobaczmy, co możemy zrobić, aby ta funkcja była mniej uciążliwa. To rozwiązanie składa się z 2 części: 1) stylu CSS z właściwością filter oraz 2) definicji filtra SVG, która jest obecnie częścią 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>

Unikanie zależności od formatu SVG w dokumencie

Zacznijmy od części 2: jak uniknąć dodania pliku SVG do modelu DOM? Jednym z nich jest przeniesienie ich do osobnego pliku SVG. Możemy skopiować <svg>…</svg> z powyższego kodu HTML i zapisać go jako filter.svg, ale najpierw musimy wprowadzić pewne zmiany. Napisy SVG w kodzie HTML są zgodne z regułami analizy HTML. Oznacza to, że w niektórych przypadkach możesz pominąć wartości atrybutów w cudzysłowach. Pliki SVG w osobnych plikach powinny być jednak prawidłowym kodem XML, a analiza XML jest o wiele bardziej rygorystyczna niż w przypadku HTML. Oto fragment kodu SVG-in-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>

Aby utworzyć ten osobny, samodzielny plik SVG (a tym samym kod XML), musimy wprowadzić pewne zmiany. Czy potrafisz zrozumieć, co?

<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>

Pierwszą zmianą jest deklaracja przestrzeni nazw XML na górze. Drugie dodanie to tak zwany „solidus” – ukośnik wskazujący, że tag <feColorMatrix> otwiera i zamyka element. Ta ostatnia zmiana nie jest konieczna (moglibyśmy po prostu zachować jawny tag zamykający </feColorMatrix>), ale ponieważ kod XML i SVG-in-HTML obsługują ten skrót />, możemy go równie dobrze wykorzystać.

Po wprowadzeniu tych zmian możemy w końcu zapisać ten plik jako prawidłowy plik SVG i wskazać go za pomocą wartości właściwości CSS filter w dokumencie HTML:

<style>
  :root {
    filter: url(filters.svg#deuteranopia);
  }
</style>

Hurra, nie musimy już wstrzykiwać formatu SVG do dokumentu. To już znacznie lepiej. Ale... teraz polegamy na osobnym pliku. To wciąż zależność. Czy możemy jakoś się z niego pozbyć?

Okazuje się, że w rzeczywistości plik nie jest potrzebny. Możemy zakodować cały plik w adresie URL za pomocą adresu URL danych. W tym celu dosłownie bierzemy pod uwagę zawartość pliku SVG, dodajemy prefiks data:, konfigurujemy odpowiedni typ MIME i otrzymujemy prawidłowy adres URL danych, który reprezentuje ten sam plik 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>

Zaletą jest to, że teraz nie trzeba już nigdzie przechowywać pliku ani ładować go z dysku lub przez sieć tylko po to, aby wykorzystać go w dokumencie HTML. Zamiast więc odwoływać się do nazwy pliku jak poprzednio, możemy wskazać adres URL danych:

<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>

Na końcu adresu URL nadal określamy identyfikator filtra, którego chcesz użyć, tak jak poprzednio. Pamiętaj, że nie musisz kodować w adresie URL dokumentu SVG w standardzie Base64, bo spowodowałoby to tylko pogorszenie czytelności i zwiększenie rozmiaru pliku. Na końcu każdego wiersza dodaliśmy ukośnik lewy, aby znaki nowego wiersza w adresie URL danych nie kończyły literału ciągu CSS.

Do tej pory mówiliśmy jedynie o tym, jak za pomocą technologii internetowej symulować wady wzroku. Co ciekawe, ostateczna implementacja w mechanizmie renderowania Blink jest dość podobna. Oto narzędzie pomocnicze w języku C++, które dodaliśmy do tej samej techniki, aby utworzyć adres URL danych z określoną definicją filtra:

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;
}

A oto, jak wykorzystujemy go do tworzenia wszystkich potrzebnych filtrów:

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 "";
  }
}

Ta technika daje nam dostęp do wszystkich możliwości filtrów SVG bez konieczności ponownej implementacji czy tworzenia nowych kół. Wdrażamy funkcję renderowania Blink, ale robimy to za pomocą platformy internetowej.

Ustaliliśmy więc, jak utworzyć filtry SVG i przekształcić je w adresy URL danych, których będziemy mogli użyć w wartości właściwości CSS filter. Czy przychodzi Ci do głowy jakiś problem z tą techniką? Okazuje się, że nie możemy polecać we wszystkich przypadkach adresu URL danych, który jest ładowany, ponieważ strona docelowa może mieć Content-Security-Policy, który blokuje adresy URL danych. W naszej ostatecznej implementacji na poziomie Blink zwracamy szczególną uwagę na to, aby podczas wczytywania pomijać CSP w przypadku tych „wewnętrznych” adresów URL.

Poza tym robimy duże postępy. Ponieważ w tym samym dokumencie nie trzeba już umieszczać wbudowanej funkcji <svg>, ograniczyliśmy nasze rozwiązanie do pojedynczej, niezależnej definicji właściwości CSS filter. Świetnie. Teraz zrobimy to samo.

Unikanie zależności CSS w dokumencie

Podsumowując, jesteśmy na tym etapie:

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

Nadal polegamy na tej właściwości CSS filter, która może zastąpić właściwość filter w prawdziwym dokumencie i zakłócić działanie. Pojawia się też podczas sprawdzania stylów obliczeniowych w Narzędziach deweloperskich, co bywało mylące. Jak możemy ich uniknąć? Musimy znaleźć sposób na dodanie filtra do dokumentu, który nie będzie automatycznie widoczny dla programistów.

Wpadliśmy na pomysł, aby utworzyć nową, wewnętrzną właściwość CSS Chrome, która działa jak filter, ale ma inną nazwę, np. --internal-devtools-filter. Następnie możemy dodać specjalne mechanizmy logiczne, aby ta właściwość nigdy nie pojawiała się w Narzędziach deweloperskich ani w stylach obliczeniowych w DOM. Mogliśmy nawet mieć pewność, że działa tylko z tym jednym elementem, którego potrzebujemy, czyli elementem głównym. Jednak to rozwiązanie nie byłoby idealne: powielalibyśmy funkcje, które już istnieją w usłudze filter, a nawet jeśli będziemy starali się ukryć tę niestandardową usługę, programiści stron internetowych i tak mogli się o niej dowiedzieć i zacząć z niej korzystać, co byłoby niekorzystne dla platformy internetowej. Potrzebujemy innego sposobu stosowania stylu CSS, który nie jest widoczny w DOM. Jakieś pomysły?

Specyfikacja CSS zawiera sekcję z wykorzystywanym modelem formatowania wizualnego, a jednym z najważniejszych pojęć jest widoczny obszarwidoczny. Jest to widok, przez który użytkownicy przeglądają stronę internetową. Ściśle powiązane pojęcie to początkowy blok, który przypomina stylowy widoczny obszar <div> dostępny tylko na poziomie specyfikacji. Specyfikacja odnosi się do koncepcji „widocznego obszaru” w różnych miejscach. Wiesz na przykład, jak przeglądarka wyświetla paski przewijania, gdy treść nie mieści się w dowolnym miejscu? Wszystko to definiuje się w specyfikacji usługi porównywania cen na podstawie tego „widocznego obszaru”.

Ten obiekt viewport istnieje również w mechanizmie renderowania Blink oraz szczegóły implementacji. Oto kod, który stosuje domyślne style widocznego obszaru zgodnie ze specyfikacją:

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;
}

Nie trzeba znać języka C++ ani niuansów mechanizmu stylów Blink, by wiedzieć, że ten kod obsługuje elementy z-index, display, position i overflow widocznego obszaru (a dokładniej – początkowy element zawierający blok). To wszystkie koncepcje, które możesz znać ze strony CSS. Jest jeszcze kilka innych magii związanych z układaniem kontekstów, które nie przekładają się bezpośrednio na właściwość CSS, ale ogólnie rzecz biorąc, obiekt viewport można wyobrazić sobie jako element, który można określić za pomocą CSS w Blink, tak jak element DOM, ale nie jest on częścią DOM.

Dzięki temu uzyskujemy dokładnie to, czego chcemy. Do obiektu viewport możemy zastosować style filter, co wizualnie wpływa na renderowanie, nie zakłócając w żaden sposób dostrzegalnych stylów strony ani DOM.

Podsumowanie

Podsumowując, na początku zaczęliśmy od zbudowania prototypu z wykorzystaniem technologii internetowej zamiast języka C++, a następnie przenieśliśmy jego fragmenty do mechanizmu renderowania Blink.

  • Na początku prototyp uczyniliśmy go bardziej samodzielnym dzięki wbudowanym w informacje adresem URL.
  • Następnie zadbaliśmy o to, aby adresy URL danych wewnętrznych były przyjazne dla CSP, przez wprowadzenie odpowiedniej wielkości liter.
  • Wprowadziliśmy w naszej implementacji niezależnej od DOM i uczyniliśmy ją nieobserwacyjną w sposób zautomatyzowany, przenosząc style do interfejsu viewport Blink-internal.

Wyjątkową cechą tej implementacji jest to, że nasz prototyp HTML/CSS/SVG wpłynął na ostateczny projekt techniczny. Udało nam się wykorzystać platformę internetową, nawet w ramach mechanizmu renderowania Blink.

Więcej informacji znajdziesz w naszej propozycji projektu oraz w artykule o błędzie śledzenia w Chromium, który zawiera informacje o wszystkich powiązanych poprawkach.

Pobierz kanały podglądu

Zastanów się, czy nie ustawić Chrome w wersji Canary, Dev lub beta jako domyślnej przeglądarki do programowania. Te kanały wersji testowej dają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platformy internetowej i wykrywanie problemów w witrynie, zanim użytkownicy ją zobaczą.

Kontakt z zespołem ds. Narzędzi deweloperskich w Chrome

Skorzystaj z poniższych opcji, aby porozmawiać o nowych funkcjach i zmianach w poście lub o innych kwestiach związanych z Narzędziami deweloperskimi.

  • Prześlij nam sugestię lub opinię na crbug.com.
  • Aby zgłosić problem z Narzędziami deweloperskimi, kliknij Więcej opcji   Więcej > Pomoc > Zgłoś problemy z Narzędziami deweloperskimi w Narzędziach deweloperskich.
  • Opublikuj tweeta na stronie @ChromeDevTools.
  • Napisz komentarz pod filmem dotyczącym nowości w Narzędziach deweloperskich w Narzędziach deweloperskich w YouTube lub filmach w YouTube ze wskazówkami dotyczącymi Narzędzi deweloperskich.