Blink レンダラで色覚異常をシミュレートする

この記事では、色覚異常のシミュレーションを DevTools と Blink レンダラに実装した理由とその方法について説明します。

背景: 色のコントラストが悪い

低コントラスト テキストは、ウェブ上で自動的に検出されるユーザー補助の問題で、最もよくある問題です。

ウェブ上のユーザー補助に関する一般的な問題のリスト。間違いなく最もよくある問題は、低コントラストのテキストです。

WebAIM の上位 100 万件のウェブサイトに対するアクセシビリティ分析によると、ホームページの 86% 以上でコントラストが低くなっています。平均すると、各ホームページに低コントラストのテキストが 36 個あります。

DevTools を使用してコントラストの問題を検出、理解、修正する

Chrome DevTools を使用すると、デベロッパーやデザイナーはコントラストを改善し、ウェブアプリのカラーパターンを使いやすくすることができます。

最近このリストに新しいツールが追加されました。このツールは他のツールとは多少異なります。上記のツールは主に、コントラスト比情報の表示に重点を置いて、その修正オプションを提供します。私たちは、デベロッパーがこの問題空間をより深く理解する手段が DevTools にまだ欠けていることに気づきました。これに対処するために、DevTools の [Rendering] タブに視覚障害シミュレーションを実装しました。

Puppeteer では、新しい page.emulateVisionDeficiency(type) API を使用して、これらのシミュレーションをプログラムで有効にできます。

色覚異常

およそ 20 人に 1 人が色覚異常を患っています(正確性は低い「色覚異常」とも呼ばれます)。このような障がいがあると、色の区別が難しくなり、コントラストの問題が増幅される可能性があります

<ph type="x-smartling-placeholder">
</ph> 色覚異常をシミュレートしていない、溶けたクレヨンの色鮮やかな写真 <ph type="x-smartling-placeholder">
</ph> 色覚異常をシミュレートしていない、カラフルな溶けたクレヨンの写真
で確認できます。
<ph type="x-smartling-placeholder">
</ph> ALT_TEXT_HERE <ph type="x-smartling-placeholder">
</ph> 溶けたクレヨンの色鮮やかな写真に色覚異常をシミュレートすることの影響。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> 溶けたクレヨンの色鮮やかな写真に、2 色覚のシミュレーションが与える影響。 <ph type="x-smartling-placeholder">
</ph> 溶けたクレヨンの色鮮やかな写真に、2 色覚のシミュレーションが与える影響。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> 溶けたクレヨンの色とりどりの絵に、1 型 2 色覚のシミュレーションが加わった影響。 <ph type="x-smartling-placeholder">
</ph> 溶けたクレヨンの色とりどりの絵に、1 型 2 色覚のシミュレーションが加わった影響。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> 溶けたクレヨンの色とりどりの絵に三角視のシミュレーションが加わった影響。 <ph type="x-smartling-placeholder">
</ph> 溶けたクレヨンの色とりどりの絵に三角視のシミュレーションが加わった影響。

通常の視力を持つデベロッパーの場合、見た目に問題はないカラーペアのコントラスト比が DevTools で不適切になることがあります。これは、コントラスト比の式がこうした色覚の欠陥を考慮しているためです。場合によっては、あなたが低コントラストのテキストを読むことができる場合もありますが、視覚障がいのあるユーザーにはその権限はありません。

デザイナーやデベロッパーがこうした視覚障がいの影響を自分のウェブアプリでシミュレーションできるようにすることで、欠けている部分を提供することを目指しています。DevTools は、コントラストの問題の発見修正に役立つだけでなく、問題を理解することもできます。

HTML、CSS、SVG、C++ を使用して色覚異常をシミュレーションする

Blink レンダラの機能の実装に入る前に、ウェブ テクノロジーを使用して同等の機能を実装する方法を理解しておきましょう。

これらの色覚異常シミュレーションは、それぞれページ全体を覆うオーバーレイだと考えることができます。ウェブ プラットフォームには、そのための手段として CSS フィルタがあります。CSS filter プロパティでは、blurcontrastgrayscalehue-rotate など、事前定義されたフィルタ関数を使用できます。さらに細かく制御するために、filter プロパティは、カスタム SVG フィルタ定義を指す URL も受け入れます。

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

上記の例では、カラー マトリックスに基づくカスタム フィルタの定義を使用しています。概念的には、各ピクセルの [Red, Green, Blue, Alpha] の色値が行列乗算され、新しい色 [R′, G′, B′, A′] が生成されます。

行列の各行には 5 つの値が含まれます。1 つは(左から右に)R、G、B、A の乗数と、定数シフト値の 5 番目の値です。行は 4 つあります。行列の最初の行は新しい赤の値、2 番目の行は Green、3 番目の行は Blue、最後の行は Alpha の計算に使用されます。

この例の正確な数値の根拠を知りたい方もいるかもしれません。このカラー マトリックスが第二色覚を適切に近似できる理由は何でしょうか。答えは科学です!値は、Machado、Oliveira、Fernandes による生理学的に正確な色覚異常シミュレーション モデルに基づいています。

いずれにせよ、この SVG フィルタを作成したので、CSS を使用してページ上の任意の要素にこのフィルタを適用できます。他の視覚障がいについても、同じパターンを繰り返すことができます。次のようなデモをご覧ください。

必要に応じて、次のように DevTools 機能を構築できます。ユーザーが DevTools UI で視覚障がいをエミュレートしたときに、検査対象のドキュメントに SVG フィルタを挿入し、ルート要素にフィルタ スタイルを適用します。ただし、この方法にはいくつかの問題があります。

  • ページのルート要素にすでにフィルタが設定されている場合は、コードがオーバーライドすることがあります。
  • ページにすでに id="deuteranopia" の要素があり、フィルタ定義と競合している可能性があります。
  • ページが特定の DOM 構造に依存している場合、<svg> を DOM に挿入すると、この仮定に違反する可能性があります。

エッジケースは別として、このアプローチの主な問題は、プログラムで監視可能な変更をページに加えることです。DevTools のユーザーが DOM を検査すると、自分で追加していない <svg> 要素や、自分で記述していない CSS filter が突然表示されることがあります。わかりにくいですね。この機能を DevTools に実装するには、こうした欠点のないソリューションが必要です。

煩わしさを軽減する方法を見てみましょう。このソリューションには、非表示にする必要がある 2 つの部分があります。1)filter プロパティを持つ CSS スタイル、2)SVG フィルタ定義(現在 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>

ドキュメント内の SVG 依存関係の回避

パート 2 から始めましょう。SVG を DOM に追加しないようにする方法は?別の SVG ファイルに移動するのも一つの手です。上記の HTML から <svg>…</svg> をコピーして、filter.svg として保存します。ただし、最初に変更を加える必要があります。HTML 内のインライン SVG は、HTML 解析ルールに従います。そのため、場合によっては属性値を囲む引用符を省略するといった方法があります。ただし、個別のファイルに含まれる SVG は有効な XML であると想定されており、XML 解析は HTML よりもはるかに厳格です。もう一度 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>

有効なスタンドアロンの 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>

最初の変更は、上部にある XML 名前空間宣言です。2 つ目は、いわゆる「solidus」です。これは、<feColorMatrix> タグが要素の開始と終了の両方を示すスラッシュです。この最後の変更は実際には必須ではありません(代わりに明示的な </feColorMatrix> 終了タグをそのまま使用することもできます)。ただし、XML と SVG-in-HTML はどちらもこの /> 省略形をサポートしているため、使用してもよいでしょう。

いずれにせよ、これらの変更を行うことで、最終的にこれを有効な SVG ファイルとして保存し、HTML ドキュメントの CSS filter プロパティ値から参照できるようになります。

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

ドキュメントに SVG を挿入する必要がなくなりました。これは、すでにかなり改善されています。しかし...今は別のファイルに依存しています。これも依存関係です。どうにかして取り除くことはできるでしょうか?

結局のところ、実際にはファイルは必要ありません。データの URL を使用して、URL 内のファイル全体をエンコードできます。これを実現するには、文字どおり SVG ファイルの内容を文字どおり取得し、data: 接頭辞を追加して適切な MIME タイプを設定します。これにより、まったく同じ SVG ファイルを表す有効なデータ 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>

その利点は、HTML ドキュメントで使用するためだけにファイルを保存したり、ディスクやネットワーク経由でファイルを読み込んだりする必要がなくなることです。以前のようにファイル名を参照するのではなく、データ URL を指すようになりました。

<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 の最後に、使用するフィルタの ID を指定します。なお、URL で SVG ドキュメントを Base64 でエンコードする必要はありません。このようにすると、可読性が損なわれ、ファイルサイズが増大するだけです。データ URL の改行文字によって CSS 文字列リテラルが終わらないように、各行の末尾にバックスラッシュを追加しました。

ここまでは、ウェブ テクノロジーを使用して視覚障がいをシミュレーションする方法についてのみ説明してきました。興味深いことに、Blink レンダラでの最終的な実装は、実際にはかなり類似しています。同じ手法に基づいて、特定のフィルタ定義でデータ URL を作成するために追加した C++ ヘルパー ユーティリティを次に示します。

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

ここでは、これを使用して必要なすべてのフィルタを作成する方法を説明します。

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

このテクニックを使えば、何も再実装したり、ホイールを作り直したりすることなく、SVG フィルタを最大限に活用できます。現在、Blink レンダラ機能を実装していますが、ウェブ プラットフォームを利用して実装しています。

これで、SVG フィルタを作成して、CSS の filter プロパティ値内で使用できるデータ URL に変換する方法が完成しました。この手法には何か問題がありますか?結局のところ、ターゲット ページにデータ URL をブロックする Content-Security-Policy が含まれている可能性があるため、すべてのケースでデータ URL が読み込まれるかどうかに依存できるわけではありません。最終的な Blink レベルの実装では、読み込み時にこれらの「内部」データ URL の CSP をバイパスするために特別な注意を払っています。

エッジケースはさておき、ある程度進展しています。インライン <svg> が同じドキュメントに存在することに依存しなくなったため、ソリューションを 1 つの自己完結型の CSS filter プロパティ定義に実質的に削減しました。これでこれもなくしましょう。

ドキュメント内の CSS 依存関係の回避

ここまでの内容をまとめます。

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

引き続きこの CSS filter プロパティに依存しているため、実際のドキュメントでは filter がオーバーライドされ、処理が壊れる可能性があります。また、DevTools で計算済みのスタイルを調べる際にも表示され、混乱を招きます。このような問題を回避するにはどうすればよいでしょうか?デベロッパーがプログラムで監視できないように、ドキュメントにフィルタを追加する方法を見つける必要があります。

一つのアイデアとして、Chrome 内部の新しい CSS プロパティを作成することにしました。このプロパティは、filter のように動作し、--internal-devtools-filter のような名前が異なります。次に、このプロパティが DevTools や DOM の計算済みスタイルに表示されないように、特別なロジックを追加できます。さらに、必要な 1 つの要素、つまりルート要素でのみ、関数が動作するようにすることもできます。ただし、このソリューションは理想的ではありません。filter ですでに存在する機能と複製するため、この標準以外のプロパティを非表示にしようとしても、ウェブ デベロッパーがそれに気づいて使用し始める可能性があるため、ウェブ プラットフォームにとっては不利です。CSS スタイルを DOM で監視できないように、別の方法で適用する必要があります。どうすればよいですか?

CSS 仕様には、使用するビジュアル フォーマット モデルを紹介するセクションがあり、その重要なコンセプトの一つがビューポートです。これは、ユーザーがウェブページを参照する画面です。密接に関連するコンセプトとして、初期包含ブロックがあります。これは、仕様レベルでのみ存在するスタイル設定が可能なビューポート <div> のようなものです。仕様では、この「ビューポート」の概念をあらゆるところで呼んでいます。たとえば、コンテンツが収まらないときにブラウザがスクロールバーを表示する方法をご存知ですか?これはすべて、この「ビューポート」に基づいて CSS 仕様で定義されています。

この viewport は、実装の詳細と同様、Blink レンダラ内にも存在します。この仕様に従ってデフォルトのビューポート スタイルを適用するコードは次のとおりです

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

C++ や Blink のスタイル エンジンの複雑さを理解していなくても、このコードがビューポート(より正確には、ブロックのイニシャルを含む)の z-indexdisplaypositionoverflow を処理することがわかります。これらはすべて、CSS で馴染みのあるコンセプトです。コンテキストの積み重ねに関しては、他にも CSS プロパティに直接変換できない点があります。全体として、この viewport オブジェクトは、DOM の一部ではないことを除けば、DOM 要素と同様に、Blink 内から CSS を使用してスタイル設定できるものと考えることができます。

これで、まさに求めている結果が得られました。filter スタイルを viewport オブジェクトに適用すると、監視可能なページスタイルや DOM に一切干渉することなく、レンダリングに視覚的に影響を及ぼします。

まとめ

ここで少しの道のりをまとめると、私たちはまず C++ ではなくウェブ テクノロジーを使用してプロトタイプを作成し、その一部を Blink レンダラに移行する作業を開始しました。

  • 私たちはまず、データ URL をインライン化することで、プロトタイプをより自己完結型にしました。
  • そして、内部データ URL の読み込みを特殊な大文字にすることで、CSP にとって使いやすいものにしました。
  • スタイルを Blink-internal viewport に移動することで、実装を DOM に依存せず、プログラムから監視できないようにしました。

この実装の特徴は、HTML/CSS/SVG プロトタイプが最終的な技術設計に影響を与えたことです。Blink レンダラ内でも、ウェブ プラットフォームを使用する方法を見つけました。

詳しい背景情報については、Google の設計案、または関連するすべてのパッチが言及されている Chromium トラッキング バグをご覧ください。

プレビュー チャンネルをダウンロードする

デフォルトの開発ブラウザとして Chrome の CanaryDev、または Beta を使用することを検討してください。これらのプレビュー チャンネルを使用すると、DevTools の最新機能にアクセスしたり、最先端のウェブ プラットフォーム API をテストしたり、ユーザーに先駆けてサイトの問題を検出したりできます。

Chrome DevTools チームへのお問い合わせ

以下のオプションを使用して、投稿の新機能や変更点、または DevTools に関連するその他のことについて話し合います。

  • ご提案やフィードバックは、crbug.com からお送りください。
  • DevTools の問題を報告するには、その他のオプションもっと見る) >ヘルプ >DevTools で DevTools の問題を報告します。
  • @ChromeDevTools でツイートしてください。
  • DevTools の新機能に関する YouTube 動画または DevTools のヒントの YouTube 動画にコメントを残してください。