Menyimulasikan kekurangan penglihatan warna di Blink Renderer

Artikel ini menjelaskan alasan dan cara kami mengimplementasikan simulasi kekurangan penglihatan warna di DevTools dan Perender Blink.

Latar belakang: kontras warna buruk

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

Daftar masalah aksesibilitas umum di web. Teks yang memiliki 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 yang rendah. Rata-rata, setiap halaman beranda memiliki 36 instance teks kontras rendah yang berbeda.

Menggunakan DevTools untuk menemukan, memahami, dan memperbaiki masalah kontras

Chrome DevTools dapat membantu developer dan desainer untuk 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 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 mengatasinya, kami mengimplementasikan simulasi kekurangan 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 menderita kekurangan penglihatan warna (juga dikenal sebagai istilah yang kurang akurat "buta warna"). Gangguan tersebut mempersulit untuk membedakan warna, sehingga dapat memperbesar masalah kontras.

Gambar warna-warni krayon yang meleleh, tanpa kekurangan penglihatan warna, disimulasikan
Gambar krayon yang meleleh penuh warna, tanpa mengalami kekurangan penglihatan warna.
ALT_TEXT_HERE
Dampak simulasi akromatopsia pada gambar krayon yang meleleh berwarna.
Dampak simulasi deuteranopia pada gambar berwarna dari krayon yang meleleh.
Dampak simulasi deuteranopia pada gambar berwarna dari krayon yang meleleh.
Dampak simulasi protanopia pada gambar warna-warni krayon yang meleleh.
Dampak simulasi protanopia pada gambar warna-warni krayon yang meleleh.
Dampak simulasi tritanopia pada gambar krayon yang meleleh berwarna.
Dampak simulasi tritanopia pada gambar krayon yang meleleh berwarna.

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 menyediakan bagian yang hilang: DevTools tidak hanya dapat membantu Anda menemukan dan memperbaiki masalah kontras, kini Anda juga dapat memahaminya.

Menyimulasikan 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 masing-masing 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 yang telah ditentukan sebelumnya, 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 menghasilkan [R′, G′, B′, A′] warna baru.

Setiap baris dalam matriks berisi 5 nilai: pengali 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 angka tepat dalam contoh kami berasal. 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 pada halaman menggunakan CSS. Kita bisa mengulangi pola yang sama untuk kekurangan penglihatan lainnya. Berikut ini demo tampilannya:

Jika mau, kita bisa membangun fitur DevTools sebagai berikut: saat pengguna mengemulasi kekurangan penglihatan di UI DevTools, kita menyuntikkan 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 bergantung pada struktur DOM tertentu, dan dengan memasukkan <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 mereka tambahkan, atau filter CSS yang tidak pernah mereka tulis. Itu akan membingungkan! Untuk mengimplementasikan fungsi ini di DevTools, kita memerlukan solusi yang tidak memiliki kekurangan ini.

Mari kita lihat cara untuk membuat hal ini tidak terlalu mengganggu. Ada dua bagian dari 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 cara menghindari penambahan SVG ke DOM? Salah satu idenya adalah memindahkannya ke file SVG terpisah. Kita dapat menyalin <svg>…</svg> dari HTML di atas dan menyimpannya sebagai filter.svg, tetapi kita perlu membuat beberapa perubahan terlebih dahulu. SVG inline di HTML mengikuti aturan penguraian HTML. Artinya, Anda dapat menghindari 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 ini valid (dan juga XML), kita perlu membuat 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 hanya bisa tetap menggunakan tag penutup </feColorMatrix> eksplisit), tetapi karena XML dan SVG-in-HTML mendukung singkatan /> ini, kita mungkin juga akan memanfaatkannya.

Bagaimanapun, dengan perubahan tersebut, kita akhirnya dapat menyimpan ini sebagai file SVG yang valid, dan mengarahkannya dari nilai properti filter CSS dalam dokumen HTML kita:

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

Hore, kita tidak perlu lagi memasukkan SVG ke dalam dokumen! Hal ini jauh lebih baik. Tapi... kita sekarang bergantung pada file yang terpisah. Hal itu masih menjadi ketergantungan. Bisakah kita menyingkirkannya?

Ternyata, kita sebenarnya tidak membutuhkan file. Kita dapat mengenkode seluruh file dalam URL dengan menggunakan URL data. Untuk melakukannya, kita secara harfiah mengambil konten file SVG yang kita miliki sebelumnya, menambahkan awalan data:, mengonfigurasi jenis MIME yang tepat, dan kita telah mendapatkan URL data 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 menunjuk 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 masih menentukan ID filter yang ingin digunakan, seperti sebelumnya. Perhatikan bahwa Anda tidak perlu mengenkode dokumen SVG menggunakan Base64 di URL—melakukannya hanya akan berdampak buruk pada keterbacaan dan meningkatkan ukuran file. Kami menambahkan garis miring terbalik di akhir setiap baris untuk memastikan karakter baris baru dalam URL data tidak menghentikan literal string CSS.

Sejauh ini, kita hanya membahas cara menyimulasikan kekurangan penglihatan menggunakan teknologi web. Menariknya, implementasi akhir kami di Blink Renderer sebenarnya sangat mirip. Berikut adalah utilitas bantuan 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 kami 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 Perender Blink, tetapi kami melakukannya dengan memanfaatkan Platform Web.

Oke, 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 mengandalkan URL data yang dimuat dalam semua kasus, karena halaman target mungkin memiliki Content-Security-Policy yang memblokir URL data. Penerapan tingkat Blink akhir kami sangat berhati-hati untuk mengabaikan CSP untuk URL data “internal” ini selama pemuatan.

Selain kasus ekstrem, kami telah membuat beberapa progres yang bagus. Karena tidak lagi bergantung pada keberadaan <svg> inline dalam dokumen yang sama, kami telah secara efektif mengurangi solusi menjadi hanya satu definisi properti filter CSS yang lengkap. Bagus! Sekarang mari kita hilangkan juga.

Menghindari dependensi CSS dalam dokumen

Sekadar merangkum, pencapaian kita sejauh ini adalah:

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

Kita masih bergantung pada properti filter CSS ini, yang mungkin mengganti filter dalam dokumen sebenarnya dan merusak apa pun. Ini juga akan muncul saat memeriksa gaya komputasi di DevTools, yang akan membingungkan. Bagaimana kita dapat 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. Selanjutnya kita dapat menambahkan logika khusus untuk memastikan properti ini tidak pernah muncul di DevTools atau dalam gaya terkomputasi 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 cara lain untuk menerapkan gaya CSS tanpa dapat diamati di DOM. Ada ide?

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 seperti <div> area tampilan yang dapat ditata gayanya yang hanya ada di tingkat spesifikasi. Spesifikasi mengacu pada konsep "area pandang" ini di mana-mana. Misalnya, Anda tahu bagaimana browser menampilkan scrollbar saat konten tidak sesuai? Semua ini ditentukan dalam spesifikasi CSS, berdasarkan "area pandang" ini.

viewport ini juga ada dalam Perender Blink, sebagai detail implementasi. Berikut ini 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 itu adalah konsep yang mungkin sudah Anda pahami dari CSS. Ada beberapa keajaiban lain terkait konteks penumpukan, yang tidak secara langsung diterjemahkan ke properti CSS, tetapi secara keseluruhan Anda dapat menganggap objek viewport ini sebagai sesuatu yang dapat ditata gayanya menggunakan CSS dari dalam Blink, seperti elemen DOM—tetapi ini bukan bagian dari DOM.

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

Kesimpulan

Sebagai rangkuman perjalanan kecil ini, kami memulai dengan membangun prototipe menggunakan teknologi web, bukan C++, dan kemudian mulai memindahkan bagian-bagiannya ke Blink Renderer.

  • Pertama-tama, kami membuat prototipe lebih mandiri dengan membuat URL data menjadi inline.
  • Kami kemudian membuat URL data internal tersebut ramah CSP, dengan menempatkan pemuatannya secara khusus.
  • Kita membuat implementasi agnostik DOM dan tidak dapat diamati secara terprogram dengan memindahkan gaya ke viewport internal Blink.

Yang unik tentang penerapan ini adalah prototipe HTML/CSS/SVG kami pada akhirnya memengaruhi desain teknis akhir. Kami menemukan cara untuk menggunakan Platform Web, bahkan dalam Perender Blink!

Untuk informasi selengkapnya, lihat proposal desain kami atau bug pelacakan Chromium yang merujuk semua patch terkait.

Mendownload saluran pratinjau

Pertimbangkan untuk menggunakan Chrome Canary, Dev, atau Beta sebagai browser pengembangan default Anda. Saluran pratinjau ini memberi Anda akses ke fitur DevTools terbaru, menguji API platform web mutakhir, dan menemukan masalah di situs Anda sebelum pengguna melakukannya.

Menghubungi tim Chrome DevTools

Gunakan opsi berikut untuk membahas fitur dan perubahan baru dalam postingan, atau hal lain yang terkait dengan DevTools.

  • Kirim saran atau masukan kepada kami melalui crbug.com.
  • Laporkan masalah DevTools menggunakan Opsi lainnya   Lainnya > Bantuan > Laporkan masalah DevTools di DevTools.
  • Tweet di @ChromeDevTools.
  • Tulis komentar di Video YouTube yang baru di DevTools atau Tips DevTools Video YouTube.