Menyimulasikan kekurangan penglihatan warna di Blink Renderer

Artikel ini menjelaskan alasan dan cara kami menerapkan simulasi kekurangan penglihatan warna di DevTools dan Blink Renderer.

Latar belakang: kontras warna buruk

Teks kontras rendah adalah masalah aksesibilitas yang paling umum dan dapat terdeteksi secara otomatis di web.

Daftar masalah aksesibilitas umum di web. Teks kontras rendah sejauh ini merupakan masalah yang paling umum.

Menurut analisis aksesibilitas WebAIM terhadap 1 juta situs teratas, lebih dari 86% halaman beranda memiliki kontras rendah. Rata-rata, setiap halaman beranda memiliki 36 instance berbeda teks kontras rendah.

Menggunakan DevTools untuk menemukan, memahami, dan memperbaiki masalah kontras

Chrome DevTools dapat membantu developer dan desainer meningkatkan kontras dan memilih skema warna yang lebih mudah diakses untuk aplikasi web:

Baru-baru ini kami menambahkan alat baru ke daftar ini, dan alat ini sedikit berbeda dari yang lain. Alat di atas terutama berfokus pada menampilkan informasi rasio kontras dan memberi Anda opsi untuk memperbaikinya. Kami menyadari bahwa DevTools masih belum menemukan cara bagi developer untuk mendapatkan pemahaman yang lebih mendalam tentang masalah ini. Untuk mengatasi hal ini, kami menerapkan simulasi kekurangan daya penglihatan di tab Rendering DevTools.

Di Puppeteer, page.emulateVisionDeficiency(type) API baru memungkinkan Anda mengaktifkan simulasi ini secara terprogram.

Kekurangan penglihatan warna

Sekitar 1 dari 20 orang mengalami kekurangan penglihatan warna (juga dikenal dengan istilah yang kurang akurat yaitu "buta warna"). Gangguan seperti itu mempersulit cara membedakan warna, yang dapat meningkatkan masalah kontras.

Gambar krayon meleleh yang berwarna-warni, tanpa simulasi kekurangan penglihatan warna
Gambar krayon meleleh yang berwarna-warni, tanpa simulasi gangguan penglihatan warna.
ALT_TEXT_HERE
Simulasi achromatopsia pada gambar krayon cair yang berwarna-warni.
Dampak simulasi deuteranopia pada gambar berwarna dari krayon yang meleleh.
Dampak simulasi deuteranopia pada gambar berwarna dari krayon yang meleleh.
Dampak dari simulasi protanopia pada gambar krayon cair yang berwarna-warni.
Dampak simulasi protanopia pada gambar warna-warni krayon yang meleleh.
Dampak dari simulasi tritanopia pada gambar krayon cair yang berwarna-warni.
Dampak simulasi tritanopia pada gambar krayon cair yang berwarna-warni.

Sebagai developer dengan penglihatan normal, Anda mungkin melihat DevTools menampilkan rasio kontras yang buruk untuk pasangan warna yang secara visual terlihat bagus bagi Anda. Hal ini terjadi karena formula rasio kontras memperhitungkan kekurangan penglihatan warna ini. Dalam beberapa kasus, Anda mungkin masih dapat membaca teks kontras rendah, tetapi penyandang gangguan penglihatan tidak memiliki hak istimewa itu.

Dengan memungkinkan desainer dan developer menyimulasikan efek kekurangan penglihatan ini di aplikasi web mereka sendiri, kami ingin memberikan bagian yang hilang: DevTools tidak hanya dapat membantu Anda menemukan dan memperbaiki masalah kontras, tetapi sekarang Anda juga dapat memahami masalah tersebut.

Simulasi kekurangan penglihatan warna dengan HTML, CSS, SVG, dan C++

Sebelum kita membahas implementasi fitur Blink Renderer dari fitur kami, sebaiknya Anda memahami cara menerapkan fungsi yang setara menggunakan teknologi web.

Anda dapat menganggap setiap simulasi kekurangan penglihatan warna ini sebagai overlay yang menutupi seluruh halaman. Platform Web memiliki cara untuk melakukannya: filter CSS! Dengan properti filter CSS, Anda dapat menggunakan beberapa fungsi filter standar, seperti blur, contrast, grayscale, hue-rotate, dan banyak lagi. Untuk kontrol yang lebih besar, properti filter juga menerima URL yang dapat mengarah ke definisi filter SVG kustom:

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

Contoh di atas menggunakan definisi filter kustom berdasarkan matriks warna. Secara konseptual, nilai warna [Red, Green, Blue, Alpha] piksel dikalikan dengan matriks untuk membuat [R′, G′, B′, A′] warna baru.

Setiap baris dalam matriks berisi 5 nilai: pengganda untuk (dari kiri ke kanan) R, G, B, dan A, serta nilai kelima untuk nilai pergeseran konstan. Ada 4 baris: baris pertama matriks digunakan untuk menghitung nilai Merah baru, baris kedua Hijau, baris ketiga Biru, dan baris terakhir Alfa.

Anda mungkin bertanya-tanya dari mana asal angka yang tepat dalam contoh kami. Apa yang membuat matriks warna ini mendekati deuteranopia? Jawabannya adalah: sains. Nilai ini didasarkan pada model simulasi kekurangan penglihatan warna yang akurat secara fisiologis oleh Machado, Oliveira, dan Fernandes.

Bagaimanapun, kita memiliki filter SVG ini, dan sekarang kita dapat menerapkannya ke elemen arbitrer di halaman menggunakan CSS. Kita dapat mengulangi pola yang sama untuk kekurangan penglihatan lainnya. Berikut adalah demo tampilannya:

Jika ingin, kita dapat mem-build fitur DevTools sebagai berikut: saat pengguna mengemulasi kekurangan penglihatan di UI DevTools, kita memasukkan filter SVG ke dalam dokumen yang diperiksa, lalu menerapkan gaya filter pada elemen root. Namun, ada beberapa masalah dengan pendekatan tersebut:

  • Halaman mungkin sudah memiliki filter pada elemen root-nya, yang kemudian dapat diganti oleh kode kita.
  • Halaman mungkin sudah memiliki elemen dengan id="deuteranopia", yang bertentangan dengan definisi filter kita.
  • Halaman mungkin mengandalkan struktur DOM tertentu, dan dengan menyisipkan <svg> ke dalam DOM, kita mungkin melanggar asumsi ini.

Selain kasus ekstrem, masalah utama dengan pendekatan ini adalah kita akan membuat perubahan yang dapat diamati secara terprogram pada halaman. Jika pengguna DevTools memeriksa DOM, mereka mungkin tiba-tiba melihat elemen <svg> yang tidak pernah ditambahkan, atau filter CSS yang tidak pernah ditulis. Itu akan membingungkan! Untuk menerapkan fungsi ini di DevTools, kita memerlukan solusi yang tidak memiliki kelemahan ini.

Mari kita lihat cara membuatnya tidak terlalu mengganggu. Ada dua bagian dalam solusi ini yang perlu kita sembunyikan: 1) gaya CSS dengan properti filter, dan 2) definisi filter SVG, yang saat ini merupakan bagian dari 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>

Menghindari dependensi SVG dalam dokumen

Mari kita mulai dengan bagian 2: bagaimana kita dapat menghindari penambahan SVG ke DOM? Salah satu caranya adalah dengan memindahkannya ke file SVG terpisah. Kita dapat menyalin <svg>…</svg> dari HTML di atas dan menyimpannya sebagai filter.svg—tetapi kita perlu melakukan beberapa perubahan terlebih dahulu. SVG inline di HTML mengikuti aturan penguraian HTML. Artinya, Anda dapat melakukan hal-hal seperti menghapus tanda kutip di sekitar nilai atribut dalam beberapa kasus. Namun, SVG dalam file terpisah seharusnya berupa XML yang valid—dan penguraian XML jauh lebih ketat daripada HTML. Berikut cuplikan SVG-in-HTML kami lagi:

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

Untuk membuat SVG mandiri yang valid ini (dan juga XML), kita perlu melakukan beberapa perubahan. Bisa tebak yang mana?

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

Perubahan pertama adalah deklarasi namespace XML di bagian atas. Penambahan kedua adalah apa yang disebut “solidus” — garis miring yang menunjukkan tag <feColorMatrix> membuka dan menutup elemen. Perubahan terakhir ini sebenarnya tidak diperlukan (kita cukup menggunakan tag penutup </feColorMatrix> eksplisit), tetapi karena XML dan SVG-in-HTML mendukung singkatan /> ini, sebaiknya kita menggunakannya.

Dengan perubahan tersebut, kita akhirnya dapat menyimpannya sebagai file SVG yang valid, dan mengarahkannya dari nilai properti filter CSS dalam dokumen HTML:

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

Hore, kita tidak perlu lagi memasukkan SVG ke dalam dokumen. Hasilnya sudah jauh lebih baik. Namun… sekarang kita bergantung pada file terpisah. Hal itu masih menjadi ketergantungan. Bisakah kita menghilangkannya?

Ternyata, kita tidak benar-benar memerlukan file. Kita dapat mengenkode seluruh file dalam URL menggunakan URL data. Untuk melakukannya, kita mengambil konten file SVG yang kita miliki sebelumnya, menambahkan awalan data:, mengonfigurasi jenis MIME yang sesuai, dan kita memiliki URL data yang valid yang mewakili file SVG yang sama:

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>

Manfaatnya adalah sekarang, kita tidak perlu lagi menyimpan file di mana pun, atau memuatnya dari {i>disk<i} atau melalui jaringan hanya untuk menggunakannya di dokumen HTML kita. Jadi, alih-alih merujuk ke nama file seperti yang kita lakukan sebelumnya, sekarang kita dapat mengarahkan ke URL data:

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

Di akhir URL, kita tetap menentukan ID filter yang ingin digunakan, seperti sebelumnya. Perhatikan bahwa Anda tidak perlu mengenkode dokumen SVG dalam URL dengan Base64—tindakan tersebut hanya akan mengurangi keterbacaan dan meningkatkan ukuran file. Kami menambahkan garis miring terbalik di akhir setiap baris untuk memastikan karakter baris baru di URL data tidak mengakhiri literal string CSS.

Sejauh ini, kita hanya membahas cara menyimulasikan kekurangan penglihatan menggunakan teknologi web. Menariknya, implementasi akhir kita di Blink Renderer sebenarnya cukup mirip. Berikut adalah utilitas helper C++ yang telah kami tambahkan untuk membuat URL data dengan definisi filter tertentu, berdasarkan teknik yang sama:

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

Berikut cara menggunakannya untuk membuat semua filter yang diperlukan:

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

Perhatikan bahwa teknik ini memberi kita akses ke kemampuan penuh filter SVG tanpa harus mengimplementasikan kembali apa pun atau menemukan kembali roda apa pun. Kami menerapkan fitur Blink Renderer, tetapi kami melakukannya dengan memanfaatkan Platform Web.

Baik, jadi kita telah mengetahui cara membuat filter SVG dan mengubahnya menjadi URL data yang dapat kita gunakan dalam nilai properti filter CSS. Dapatkah Anda memikirkan masalah dengan teknik ini? Ternyata, kita tidak dapat benar-benar mengandalkan URL data yang dimuat dalam semua kasus, karena halaman target mungkin memiliki Content-Security-Policy yang memblokir URL data. Implementasi tingkat Blink akhir kami memerlukan perhatian khusus untuk mengabaikan CSP untuk URL data “internal” ini selama pemuatan.

Selain kasus ekstrem, kami telah mencapai beberapa kemajuan yang baik. Karena tidak lagi bergantung pada <svg> inline yang ada dalam dokumen yang sama, kami telah secara efektif mengurangi solusi menjadi satu definisi properti filter CSS mandiri. Bagus! Sekarang, mari kita hapus juga.

Menghindari dependensi CSS dalam dokumen

Sebagai ringkasan, berikut adalah tahapan yang telah kita lalui sejauh ini:

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

Kita masih bergantung pada properti filter CSS ini, yang mungkin mengganti filter dalam dokumen sebenarnya dan merusaknya. Hal ini juga akan muncul saat memeriksa gaya terkomputasi di DevTools, yang akan membingungkan. Bagaimana cara menghindari masalah ini? Kita perlu menemukan cara untuk menambahkan filter ke dokumen tanpa hal itu dapat diamati secara terprogram oleh developer.

Salah satu ide yang muncul adalah membuat properti CSS internal Chrome baru yang berperilaku seperti filter, tetapi memiliki nama yang berbeda, seperti --internal-devtools-filter. Kemudian, kita dapat menambahkan logika khusus untuk memastikan properti ini tidak pernah muncul di DevTools atau dalam gaya yang dihitung di DOM. Kita bahkan dapat memastikan bahwa elemen ini hanya berfungsi pada satu elemen yang kita perlukan: elemen {i>root<i}. Namun, solusi ini tidak akan ideal: kami akan menduplikasi fungsi yang sudah ada dengan filter, dan meskipun kami berusaha keras untuk menyembunyikan properti non-standar ini, developer web tetap dapat mencari tahu tentang properti tersebut dan mulai menggunakannya, yang akan berdampak buruk bagi Platform Web. Kita memerlukan beberapa cara lain untuk menerapkan gaya CSS tanpa dapat diamati di DOM. Apakah Anda tahu siapa yang sebaiknya saya hubungi?

Spesifikasi CSS memiliki bagian yang memperkenalkan model pemformatan visual yang digunakannya, dan salah satu konsep utamanya adalah area pandang. Ini adalah tampilan visual tempat pengguna melihat halaman web. Konsep yang terkait erat adalah blok penampung awal, yang mirip dengan <div> area tampilan yang dapat ditata gayanya yang hanya ada di tingkat spesifikasi. Spesifikasi ini mengacu pada konsep “area pandang” ini di mana-mana. Misalnya, Anda tahu bagaimana browser menampilkan scrollbar saat konten tidak sesuai? Semuanya ditentukan dalam spesifikasi CSS, berdasarkan “area pandang” ini.

viewport ini juga ada dalam Blink Renderer, sebagai detail implementasi. Berikut kode yang menerapkan gaya area pandang default sesuai dengan spesifikasi:

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

Anda tidak perlu memahami C++ atau seluk-beluk mesin Gaya Blink untuk melihat bahwa kode ini menangani z-index, display, position, dan overflow dari area pandang (atau lebih akurat: awal yang berisi blok). Semua konsep tersebut mungkin sudah Anda pahami dari CSS. Ada beberapa hal ajaib lainnya yang terkait dengan konteks penumpukan, yang tidak secara langsung diterjemahkan ke properti CSS, tetapi secara keseluruhan, Anda dapat menganggap objek viewport ini sebagai sesuatu yang dapat diberi gaya menggunakan CSS dari dalam Blink, seperti elemen DOM—kecuali bukan bagian dari DOM.

Dengan begitu, kita akan mendapatkan apa yang kita inginkan. Kita dapat menerapkan gaya filter ke objek viewport, yang secara visual memengaruhi rendering, tanpa mengganggu gaya halaman yang dapat diamati atau DOM dengan cara apa pun.

Kesimpulan

Untuk merangkum perjalanan kecil kita di sini, kita mulai dengan membuat prototipe menggunakan teknologi web, bukan C++, lalu mulai memindahkan bagian-bagiannya ke Blink Renderer.

  • Pertama-tama, kami membuat prototipe menjadi lebih mandiri dengan menyisipkan URL data.
  • Kemudian, kami membuat URL data internal tersebut kompatibel dengan CSP, dengan kasus khusus pemuatan.
  • Kami membuat penerapan kami tidak bergantung pada DOM dan tidak dapat diamati secara terprogram dengan memindahkan gaya ke viewport internal Blink.

Hal yang unik dari implementasi ini adalah prototipe HTML/CSS/SVG kita akhirnya memengaruhi desain teknis akhir. Kami menemukan cara untuk menggunakan Platform Web, bahkan dalam Perender Blink.

Untuk mengetahui latar belakang selengkapnya, lihat proposal desain kami atau bug pelacakan Chromium yang mereferensikan semua patch terkait.

Mendownload saluran pratinjau

Sebaiknya gunakan Chrome Canary, Dev, atau Beta sebagai browser pengembangan default Anda. Saluran pratinjau ini memberi Anda akses ke fitur DevTools terbaru, memungkinkan Anda menguji API platform web canggih, dan membantu Anda menemukan masalah di situs sebelum pengguna melakukannya.

Hubungi tim Chrome DevTools

Gunakan opsi berikut untuk membahas fitur baru, update, atau apa pun yang berkaitan dengan DevTools.