Menyimulasikan kekurangan penglihatan warna di Blink Renderer

Mathias Bynens
Mathias Bynens

Artikel ini menjelaskan mengapa dan bagaimana kami mengimplementasikan simulasi kekurangan penglihatan warna di DevTools dan Blink Renderer.

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 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 berbeda untuk teks kontras rendah.

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 memunculkan informasi rasio kontras dan memberi Anda opsi untuk memperbaikinya. Kami menyadari bahwa DevTools masih belum memiliki cara bagi developer untuk mendapatkan understanding yang lebih mendalam tentang ruang masalah ini. Untuk mengatasinya, kami mengimplementasikan simulasi kekurangan penglihatan di tab Rendering DevTools.

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

Kekurangan penglihatan warna

Kira-kira 1 dari 20 orang menderita defisiensi penglihatan warna (juga dikenal sebagai istilah yang kurang akurat "buta warna"). Gangguan tersebut mempersulit cara membedakan warna, sehingga dapat memperkuat masalah kontras.

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

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. Anda mungkin masih dapat membaca teks kontras rendah dalam beberapa kasus, tetapi pengguna dengan gangguan penglihatan tidak memiliki hak istimewa tersebut.

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

Menyimulasikan defisiensi penglihatan warna dengan HTML, CSS, SVG, dan C++

Sebelum mendalami penerapan fitur Blink Renderer, kita perlu 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 punya 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] setiap piksel dikali matriks untuk membuat warna baru [R′, G′, B′, A′].

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

Anda mungkin bertanya-tanya dari mana angka yang tepat dalam contoh kita berasal. Apa yang membuat matriks warna ini merupakan perkiraan yang baik dari deuteranopia? Jawabannya adalah: sains! Nilai ini didasarkan pada model simulasi defisiensi penglihatan warna yang akurat secara fisiologis oleh Machado, Oliveira, dan Fernandes.

Bagaimanapun, kita memiliki filter SVG ini, dan kita bisa menerapkannya ke elemen arbitrer pada halaman dengan menggunakan CSS. Kita bisa mengulangi pola yang sama untuk kekurangan penglihatan lainnya. Berikut ini contoh tampilannya:

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

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

Di samping kasus ekstrem, masalah utama dengan pendekatan ini adalah kami 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 mereka tulis. Itu akan membingungkan! Untuk mengimplementasikan fungsi ini di DevTools, kita memerlukan solusi yang tidak memiliki kekurangan ini.

Mari kita lihat cara agar hal ini tidak terlalu mengganggu. Ada dua bagian pada 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 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 menghilangkan tanda kutip di sekitar nilai atribut dalam beberapa kasus. Namun, SVG di 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 (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 dapat tetap menggunakan tag penutup </feColorMatrix> eksplisit), tetapi karena XML dan SVG-in-HTML mendukung singkatan /> ini, kita mungkin juga memanfaatkannya.

Bagaimanapun, dengan perubahan tersebut, akhirnya kita dapat menyimpan file ini sebagai file SVG yang valid, dan mengarahkannya ke file tersebut 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! Ini sudah jauh lebih baik. Tapi... sekarang kita bergantung pada file yang terpisah. Itu masih ketergantungan. Bisakah kita menghapusnya?

Ternyata, kita sebenarnya tidak membutuhkan file. Kita dapat mengenkode seluruh file dalam URL menggunakan URL data. Untuk mewujudkannya, kita secara harfiah mengambil konten file SVG yang kita miliki sebelumnya, menambahkan awalan data:, mengonfigurasi jenis MIME yang tepat, dan kita 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>

Keuntungannya adalah, sekarang, kita tidak perlu lagi menyimpan file di mana pun, atau memuatnya dari disk atau melalui jaringan hanya untuk menggunakannya dalam dokumen HTML. Jadi, alih-alih merujuk ke nama file seperti 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 masih menentukan ID filter yang ingin digunakan, seperti sebelumnya. Perhatikan bahwa dokumen SVG tidak perlu dienkode menggunakan Base64 di URL—melakukannya 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 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;
}

Dan berikut cara kami menggunakannya untuk membuat semua filter yang dibutuhkan:

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, teknik ini memberi kita akses ke kekuatan penuh filter SVG tanpa harus mengimplementasikan kembali apa pun atau menciptakan kembali roda apa pun. Kami sedang menerapkan fitur Blink Renderer, tetapi kami melakukannya dengan memanfaatkan Platform Web.

Oke, jadi kita telah menemukan cara membuat filter SVG dan mengubahnya menjadi URL data yang dapat kita gunakan dalam nilai properti filter CSS. Dapatkah Anda memikirkan suatu masalah dengan teknik ini? Ternyata, kami tidak dapat benar-benar 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 berhati-hati untuk mengabaikan CSP untuk URL data "internal" ini selama pemuatan.

Di samping kasus ekstrem, kami telah membuat beberapa kemajuan yang bagus. Karena kami tidak lagi bergantung pada <svg> inline yang ada di dokumen yang sama, kami secara efektif telah mengurangi solusi kami menjadi hanya satu definisi properti filter CSS mandiri. Bagus. Sekarang mari kita singkirkan juga.

Menghindari dependensi CSS dalam dokumen

Sebagai ringkasan, sampai saat ini pencapaian kita adalah:

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

Kita masih bergantung pada properti filter CSS ini, yang mungkin mengganti filter dalam dokumen asli dan merusak sesuatu. Tanda ini juga akan muncul ketika memeriksa gaya yang dihitung di DevTools, yang akan membingungkan. Bagaimana kita dapat menghindari masalah ini? Kita perlu menemukan cara untuk menambahkan filter ke dokumen tanpa 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. Kita kemudian 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 ini hanya berfungsi pada satu elemen yang kita perlukan: elemen root. 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 masih dapat mengetahuinya dan mulai menggunakannya, yang akan berdampak buruk bagi Platform Web. Kita memerlukan cara lain untuk menerapkan gaya CSS tanpa dapat diamati dalam DOM. Ada ide?

Spesifikasi CSS memiliki bagian yang memperkenalkan model format visual yang digunakannya, dan salah satu konsep utama di sana adalah area pandang. Ini adalah tampilan visual yang digunakan pengguna untuk berkonsultasi tentang halaman web. Konsep yang terkait erat adalah blok yang berisi awal, yang menyerupai area pandang <div> yang dapat ditata yang hanya ada di level spesifikasi. Spesifikasi merujuk pada konsep “area pandang” ini di semua tempat. Misalnya, Anda tahu cara browser menampilkan scrollbar jika kontennya tidak sesuai? Ini semua ditetapkan dalam spesifikasi CSS, berdasarkan “area pandang” ini.

viewport ini juga ada di dalam Blink Renderer, sebagai detail implementasi. Berikut adalah 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: blok penampung awal). Itu semua adalah konsep yang mungkin telah 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 menggunakan CSS dari dalam Blink, seperti elemen DOM—kecuali jika objek tersebut bukan bagian dari DOM.

Dengan begitu, kita akan mendapatkan hasil seperti 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 kita di sini, kita memulai dengan membangun prototipe menggunakan teknologi web, bukan C++, dan kemudian mulai mengerjakan bagian-bagiannya yang dipindahkan ke Blink Renderer.

  • Pertama, kami membuat prototipe lebih mandiri dengan menyisipkan URL data.
  • Kami kemudian membuat URL data internal tersebut cocok untuk CSP, dengan memberi kapitalisasi khusus pada pemuatannya.
  • Kami membuat penerapan kami yang tidak bergantung pada DOM dan tidak dapat diamati secara terprogram dengan memindahkan gaya ke viewport Blink-internal.

Yang unik dari implementasi ini adalah prototipe HTML/CSS/SVG kami akhirnya memengaruhi desain teknis akhir. Kami menemukan cara untuk menggunakan Platform Web, bahkan di dalam Blink Renderer!

Untuk latar belakang selengkapnya, lihat proposal desain kami atau bug pelacakan Chromium yang merujuk ke 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 tercanggih, dan menemukan masalah di situs Anda sebelum pengguna melakukannya.

Menghubungi tim Chrome DevTools

Gunakan opsi berikut untuk membahas fitur dan perubahan baru di postingan ini, 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.
  • Tuliskan komentar di video YouTube Yang baru di DevTools atau video YouTube di DevTools.