Blink Renderer'da renk görme bozukluklarını simüle etme

Bu makalede, DevTools ve Blink Oluşturucu'ya renk görme eksikliği simülasyonunu neden ve nasıl uyguladığımız açıklanmaktadır.

Arka plan: kötü renk kontrastı

Düşük kontrastlı metin, web'de otomatik olarak algılanan en yaygın erişilebilirlik sorunudur.

Web'de sık karşılaşılan erişilebilirlik sorunlarının listesi. Düşük kontrastlı metin, en sık karşılaşılan sorundur.

WebAIM'in en popüler 1 milyon web sitesini incelediği erişilebilirlik analizine göre, ana sayfaların %86'sından fazlası düşük kontrasta sahip. Ortalama olarak her ana sayfada 36 farklı düşük kontrastlı metin örneği bulunur.

Kontrast sorunlarını bulmak, anlamak ve düzeltmek için Geliştirici Araçları'nı kullanma

Chrome Geliştirici Araçları, geliştiricilerin ve tasarımcıların kontrastı iyileştirmesine ve web uygulamaları için daha erişilebilir renk şemaları seçmesine yardımcı olabilir:

Kısa süre önce bu listeye diğerlerinden biraz farklı olan yeni bir araç ekledik. Yukarıdaki araçlar esas olarak kontrast oranı bilgilerini göstermeye ve size bu sorunu düzeltme seçenekleri sunmaya odaklanır. DevTools'ta geliştiricilerin bu sorun alanını daha derinlemesine anlayabileceği bir yöntemin eksik olduğunu fark ettik. Bu sorunu gidermek için Geliştirici Araçları Oluşturma sekmesinde görme bozukluğu simülasyonunu uyguladık.

Puppeteer'da yeni page.emulateVisionDeficiency(type) API, bu simülasyonları programatik olarak etkinleştirmenize olanak tanır.

Renk görme bozuklukları

Yaklaşık 20 kişiden 1'i renk görme bozukluğundan (daha az doğru bir terim olan "renk körlüğü" olarak da bilinir) muzdariptir. Bu tür bozukluklar, farklı renkleri ayırt etmeyi zorlaştırır ve kontrast sorunlarını artırabilir.

Renk görme bozukluğu taklit edilmemiş, erimiş boya kalemlerinin renkli resmi
Renk körlüğü eksiklikleri simüle edilmemiş, renkli bir eritilmiş boya resmi.
ALT_TEXT_HERE
Erimiş boya kalemlerinin renkli resminde renk körlüğü simülasyonunun etkisi.
Erimiş mum boyalardan oluşan renkli bir resimde deuteranopia'nın taklit edilmesinin etkisi.
Döteranopi simüle etmenin, erimiş pastel boyaların renkli resmi üzerindeki etkisi.
Erimiş mum boyalardan oluşan renkli bir resimde protanopi simülasyonunun etkisi.
Erimiş mum boyalardan oluşan renkli bir resimde protanopi simülasyonunun etkisi.
Erimiş mum boyalardan oluşan renkli bir resimde tritanopiyi simüle etmenin etkisi.
Erimiş pastel boyaların renkli resmi üzerindeki tritanopia simülasyonunun etkisi.

Normal görme yetisine sahip bir geliştirici olarak, DevTools'un sizin için görsel olarak iyi görünen renk çiftleri için kötü bir kontrast oranı gösterdiğini görebilirsiniz. Bunun nedeni, kontrast oranı formüllerinin bu renk görme eksikliklerini hesaba katmasıdır. Siz bazı durumlarda düşük kontrastlı metinleri okuyabilseniz de görme engelli kullanıcılar bu ayrıcalığa sahip değildir.

Tasarımcıların ve geliştiricilerin bu görme eksikliklerinin kendi web uygulamalarındaki etkisini simüle etmelerine olanak tanıyarak eksik olan parçayı sunmayı amaçlıyoruz: DevTools artık kontrast sorunlarını bulup düzeltmenize yardımcı olmakla kalmıyor, aynı zamanda bunları anlamanıza da yardımcı oluyor.

HTML, CSS, SVG ve C++ ile renk görme bozukluklarını taklit etme

Özelliğimizin Blink Renderer uygulaması konusuna geçmeden önce, web teknolojisini kullanarak eşdeğer işlevi nasıl uygulayacağınızı anlamamıza yardımcı olacaktır.

Bu renk körlüğü simülasyonlarından her birini, tüm sayfayı kaplayan bir yer paylaşımı olarak düşünebilirsiniz. Web Platformu'nda bunu yapmanın bir yolu var: CSS filtreleri. CSS filter özelliğiyle blur, contrast, grayscale, hue-rotate gibi önceden tanımlanmış bazı filtre işlevlerini kullanabilirsiniz. Daha da fazla kontrol için filter mülkü, özel bir SVG filtre tanımına işaret edebilecek bir URL'yi de kabul eder:

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

Yukarıdaki örnekte, renk matrisine dayalı bir özel filtre tanımı kullanılmaktadır. Kavramsal olarak, her pikselin [Red, Green, Blue, Alpha] renk değeri, yeni bir renk [R′, G′, B′, A′] oluşturmak için matris çarpımıyla çarpılır.

Matristeki her satır 5 değer içerir: (soldan sağa) R, G, B ve A için bir çarpan ve sabit bir kaydırma değeri için beşinci bir değer. 4 satır vardır: Matrisin ilk satırı yeni Kırmızı değerini hesaplamak için, ikinci satır Yeşil, üçüncü satır Mavi ve son Alfa satırı kullanılır.

Örneğimizdeki sayıların tam olarak nereden geldiğini merak ediyor olabilirsiniz. Bu renk matrisinin dötronopi için iyi bir yaklaşım olmasının nedeni nedir? Yanıt: bilim. Değerler Machado, Oliveira ve Fernandes'in geliştirdiği fizyolojik olarak doğru renk körlüğü simülasyonu modeline dayanır.

Bu SVG filtresine sahip olduğumuza göre, artık CSS kullanarak sayfadaki herhangi bir öğeye uygulayabiliriz. Aynı yöntemi diğer görme bozuklukları için de tekrarlayabiliriz. Bunun nasıl göründüğüne dair bir demoyu aşağıda bulabilirsiniz:

İstersek DevTools özelliğimizi şu şekilde oluşturabiliriz: Kullanıcı DevTools kullanıcı arayüzünde görme engelini taklit ettiğinde SVG filtresini incelenen belgeye enjekte ederiz ve ardından filtre stilini kök öğeye uygularız. Ancak bu yaklaşımla ilgili bazı sorunlar vardır:

  • Sayfanın kök öğesinde zaten bir filtre olabilir. Bu filtre, kodumuz tarafından geçersiz kılınabilir.
  • Sayfada, filtre tanımımuzla çakışıp id="deuteranopia" içeren bir öğe olabilir.
  • Sayfa belirli bir DOM yapısına bağlı olabilir ve <svg> öğesini DOM'a ekleyerek bu varsayımları ihlal edebiliriz.

Bununla birlikte, bu yaklaşımın temel sorunu, sayfada programatik olarak gözlemlenebilir değişiklikler yaptığımızdır. DevTools kullanıcısı DOM'u incelediğinde hiç eklemediği bir <svg> öğesi veya hiç yazmadığı bir CSS filter öğesi görebilir. Bu kafa karıştırıcı olur. Bu işlevi Geliştirici Araçları'na uygulamak için bu tür dezavantajları olmayan bir çözüme ihtiyacımız var.

Bunu nasıl daha az rahatsız edici hale getirebileceğimize bakalım. Bu çözümün gizlememiz gereken iki bölümü vardır: 1) filter mülküne sahip CSS stili ve 2) şu anda DOM'un parçası olan SVG filtre tanımı.

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

Belge içi SVG bağımlılığından kaçınma

2. bölümle başlayalım: SVG'yi DOM'a eklemekten nasıl kaçınabiliriz? Fikirlerden biri, dosyayı ayrı bir SVG dosyasına taşımaktır. Yukarıdaki HTML'den <svg>…</svg> öğesini kopyalayıp filter.svg olarak kaydedebiliriz ancak önce bazı değişiklikler yapmamız gerekir. HTML'de satır içi SVG, HTML ayrıştırma kurallarını izler. Yani bazı durumlarda özellik değerlerinin tırnak içine alınmaması gibi durumlarda sorun yaşamazsınız. Ancak ayrı dosyalardaki SVG'lerin geçerli XML olması gerekir ve XML ayrıştırma işlemi HTML'den çok daha katı kurallara tabidir. Yine HTML içinde SVG snippet'imizi burada görebilirsiniz:

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

Bu geçerli bağımsız SVG'yi (ve dolayısıyla XML) oluşturmak için bazı değişiklikler yapmamız gerekir. Hangisi olduğunu tahmin edebilir misiniz?

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

İlk değişiklik, en üstteki XML ad alanı beyanı. İkinci ekleme, "çizgi" olarak adlandırılan, <feColorMatrix> etiketinin öğeyi hem açtığını hem de kapattığını belirten eğik çizgidir. Bu son değişiklik aslında gerekli değildir (bunun yerine açıkça belirtilmiş </feColorMatrix> kapanış etiketine bağlı kalabiliriz), ancak hem XML hem de HTML içinde SVG bu /> kısaltmasını desteklediği için ondan da yararlanabiliriz.

Bu değişikliklerle birlikte, nihayet bunu geçerli bir SVG dosyası olarak kaydedebilir ve HTML belgemizdeki CSS filter mülk değerinden bu dosyaya işaret edebiliriz:

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

Artık dokümana SVG eklememiz gerekmiyor. Bu çok daha iyi. Ancak artık ayrı bir dosyaya ihtiyacımız var. Bu yine de bir bağımlılıktır. Bunu bir şekilde ortadan kaldırabilir miyiz?

Aslında bir dosyaya ihtiyacımız yok. Veri URL'si kullanarak dosyanın tamamını bir URL içinde kodlayabiliriz. Bunu yapmak için, daha önce sahip olduğumuz SVG dosyasının içeriğini alıp data: ön ekini ekler ve uygun MIME türünü yapılandırırız. Böylece, aynı SVG dosyasını temsil eden geçerli bir veri URL'si elde ederiz:

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>

Bu sayede, dosyayı HTML belgemizde kullanmak için artık herhangi bir yerde depolamamıza veya diskten ya da ağ üzerinden yüklememize gerek kalmadı. Bu nedenle, daha önce yaptığımız gibi dosya adını belirtmek yerine artık veri URL'sini gösterebiliriz:

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

URL'nin sonunda, kullanmak istediğimiz filtrenin kimliğini belirtmeye devam ederiz. URL'deki SVG belgesini Base64 olarak kodlamanız gerekmez. Bunu yapmak yalnızca okunabilirliği olumsuz etkiler ve dosya boyutunu artırır. Veri URL'sindeki satır sonu karakterlerinin CSS dize değişmez değerini sonlandırmaması için her satırın sonuna ters eğik çizgi ekledik.

Şu ana kadar yalnızca web teknolojisini kullanarak görme eksikliklerini nasıl simüle edeceğimizden bahsettik. İlginç bir şekilde, Blink Oluşturucu'daki nihai uygulamamız aslında oldukça benzer. Aynı tekniğe dayalı olarak belirli bir filtre tanımı içeren bir veri URL'si oluşturmak için eklediğimiz C++ yardımcı yardımcı programı aşağıda verilmiştir:

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

İhtiyacımız olan tüm filtreleri oluşturmak için bu aracı şu şekilde kullanıyoruz:

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

Bu tekniğin, herhangi bir şeyi yeniden uygulamak veya yeniden icat etmek zorunda kalmadan SVG filtrelerinin tüm gücüne erişmemizi sağladığını unutmayın. Blink Renderer özelliğini uyguluyoruz, ancak bunu Web Platformu'ndan da faydalanarak yapıyoruz.

SVG filtrelerinin nasıl oluşturulacağını ve CSS filter mülk değerimizde kullanabileceğimiz veri URL'lerine nasıl dönüştürüleceğini öğrendik. Bu teknikle ilgili aklınıza gelen bir sorun var mı? Hedef sayfada veri URL'lerini engelleyen bir Content-Security-Policy olabileceğinden, veri URL'sinin her durumda yüklenmesine güvenemeyiz. Nihai Blink düzeyindeki uygulamamızda, yükleme sırasında bu "dahili" veri URL'leri için CSP'yi atlamak üzere özel özen gösterilir.

Bununla birlikte, uç durumlar hariç iyi bir ilerleme kaydettik. Artık satır içi <svg>'ün aynı dokümanda bulunmasına bağlı olmadığımız için çözümümüzü tek bir bağımsız CSS filter mülk tanımı olarak etkili bir şekilde azalttık. Mükemmel! Şimdi de bu sorunu ortadan kaldıralım.

Doküman içi CSS bağımlılığından kaçınma

Şimdiye kadar neler yaptığımızı özetlemek isteriz:

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

Yine de bu CSS filter özelliğine bağlıyız. Bu özellik, gerçek belgedeki bir filter özelliğini geçersiz kılabilir ve işleri bozabilir. Bu durum, DevTools'ta hesaplanmış stilleri incelerken de ortaya çıkar ve kafa karıştırıcı olur. Bu sorunları nasıl önleyebiliriz? Geliştiriciler tarafından programlı olarak gözlemlenemeyecek şekilde dokümana filtre eklemenin bir yolunu bulmamız gerekiyor.

Ortaya çıkan fikirlerden biri, filter gibi davranan ancak --internal-devtools-filter gibi farklı bir ada sahip yeni bir Chrome dahili CSS özelliği oluşturmaktı. Ardından, bu özelliğin hiçbir zaman DevTools'ta veya DOM'daki hesaplanmış stillerde görünmesini engellemek için özel mantık ekleyebiliriz. Hatta bu işlevin yalnızca ihtiyacımız olan tek öğe olan kök öğede çalıştığından emin olabiliriz. Ancak bu çözüm ideal olmazdı: filter ürününde zaten var olan işlevleri yinelerdik. Standart olmayan bu mülkü gizlemeye çalışsak bile web geliştiricileri bunu tespit edip kullanmaya başlayabilir. Bu da Web Platformu için kötü bir durumdur. CSS stilini DOM'da gözlemlenebilir olmadan uygulamanın başka bir yoluna ihtiyacımız var. Bu konuda bir öneriniz var mı?

CSS spesifikasyonunda, kullanılan görsel biçimlendirme modelini tanıtan bir bölüm bulunur. Bu bölümdeki önemli kavramlardan biri görünüm alanı'dır. Bu, kullanıcıların web sayfasına danışmak için kullandığı görsel görünümdür. Yakından alakalı bir kavram, içeren ilk bloktur. Bu kavram, yalnızca spesifikasyon düzeyinde var olan, stilize edilebilir bir görüntü alanına <div> benzer. Spesifikasyonda her yerde bu "görünüm alanı" kavramına atıfta bulunulur. Örneğin, içerik sığmadığında tarayıcı kaydırma çubuklarını nasıl gösterdiğini biliyor musunuz? Bunların tümü, bu "görüntü alanına" göre CSS spesifikasyonunda tanımlanır.

Bu viewport, uygulama ayrıntısı olarak Blink Oluşturucu'da da bulunur. Varsayılan görüntü alanı stillerini spesifikasyona göre uygulayan kodu burada bulabilirsiniz:

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

Bu kodun, görüntü alanının (veya daha doğrusu, ilk kapsayıcı bloğun) z-index, display, position ve overflow değerlerini işlediğini görmek için C++'yu veya Blink'in stil motorunun inceliklerini anlamanıza gerek yoktur. Bunların hepsi CSS'den aşina olabileceğiniz kavramlar. Yığın bağlamlarıyla ilgili, doğrudan CSS özelliğine çevrilmeyen başka sihirli özellikler de vardır. Ancak genel olarak bu viewport nesnesini, DOM'un bir parçası olmaması dışında DOM öğesi gibi Blink'ten CSS kullanılarak stillendirilebilecek bir öğe olarak düşünebilirsiniz.

Bu sayede tam olarak istediğimiz sonuçları elde ediyoruz. filter stillerimizi, gözlemlenebilir sayfa stillerini veya DOM'u herhangi bir şekilde etkilemeden, oluşturmayı görsel olarak etkileyen viewport nesnesine uygulayabiliriz.

Sonuç

Bu küçük yolculuğumuzu özetlemek gerekirse, C++ yerine web teknolojisini kullanarak bir prototip oluşturarak başladık ve ardından bu prototipin bazı bölümlerini Blink Oluşturucu'ya taşımaya başladık.

  • Öncelikle, veri URL'lerini satır içine ekleyerek prototipimizi daha bağımsız hale getirdik.
  • Daha sonra, bu dahili veri URL'lerini özel olarak büyük/küçük harfe dönüştürerek CSP uyumlu hale getirdik.
  • Stilleri Blink-internal viewport öğesine taşıyarak uygulamamızı DOM'den bağımsız ve programlı olarak gözlemlenemez hale getirdik.

Bu uygulamanın benzersiz yanı, HTML/CSS/SVG prototipimizin nihai teknik tasarımı etkilemiş olmasıdır. Blink Oluşturucu'da bile Web Platformu'nu kullanmanın bir yolunu bulduk.

Daha fazla bilgi için tasarım teklifimize veya ilgili tüm yamalara referans veren Chromium izleme hatasına göz atın.

Önizleme kanallarını indirme

Chrome Canary, Yeni geliştirilenler veya Beta'yı varsayılan geliştirme tarayıcınız olarak kullanabilirsiniz. Bu önizleme kanalları, en son DevTools özelliklerine erişmenizi sağlar, en yeni web platformu API'lerini test etmenize olanak tanır ve sitenizdeki sorunları kullanıcılarınızdan önce bulmanıza yardımcı olur.

Chrome Geliştirici Araçları ekibiyle iletişime geçme

Yeni özellikler, güncellemeler veya Geliştirici Araçları ile ilgili başka herhangi bir konu hakkında konuşmak için aşağıdaki seçenekleri kullanın.