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

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

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

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

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

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

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

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

このリストに最近追加された新しいツールは、他のツールとは少し異なります。上記のツールは主に、コントラスト比に関する情報を表示し、修正するためのオプションを提供することに重点を置いています。デベロッパーがこの問題領域をより深く理解するための方法が DevTools にまだないことに気付きました。この問題に対処するため、DevTools の [レンダリング] タブに色覚異常のシミュレーションを実装しました。

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

色覚異常

およそ 20 人に 1 人は色覚特性(より正確でない別の名称として「色盲」)を持っています。色覚特性がある場合、色の違いを識別することが難しくなり、コントラストの問題による影響を大きく受けます

色覚障害をシミュレートしていない、溶けたクレヨンのカラフルな写真
色覚障がいのシミュレーションなしで、カラフルな溶けたクレヨンの画像
ALT_TEXT_HERE
色覚異常をシミュレートしたとき、溶けたクレヨンのカラフルな写真に与える影響。
溶けたクレヨンのカラフルな写真にデューテロノピーをシミュレートした影響。
溶けたクレヨンのカラフルな写真にデューテロノピーをシミュレートした場合の影響。
溶けたクレヨンのカラフルな写真にプロトアノープをシミュレートした影響。
溶けたクレヨンのカラフルな写真に 1 型 2 色覚をシミュレートした影響。
溶けたクレヨンのカラフルな写真にトリタンオペアをシミュレートした影響。
溶けたクレヨンのカラフルな写真にトリタン opia をシミュレートした場合の影響。

色覚特性のないデベロッパーには問題ないように見える色ペアでも、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 つの値が含まれています。左から右に R、G、B、A の乗数と、5 つ目の値として定数シフト値です。行は 4 つあります。行の 1 つ目は新しい赤色値の計算に使用され、2 つ目は緑色、3 つ目は青色、4 つ目はアルファ値の計算に使用されます。

この例の正確な数値はどこから来るのか、疑問に思われるかもしれません。このカラー マトリックスがデューテロノピーを近似している理由は何ですか?答えは科学です。これらの値は、Machado、Oliveira、Fernandes による生理学的に正確な色覚特性のシミュレーション モデルに基づいています。

作成した SVG フィルタは、CSS を使用してページ上の任意の要素に適用できます。他の視覚障がいについても、同じパターンを繰り返すことができます。デモは次のとおりです。

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

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

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

どのようにしてこの問題を軽減できるか、見てみましょう。このソリューションで非表示にする必要がある部分は 2 つあります。1 つは filter プロパティを持つ CSS スタイル、もう 1 つは現在 DOM の一部である SVG フィルタ定義です。

<!-- 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 の HTML は、HTML 解析ルールに従います。つまり、属性値の引用符を省略するなど、ただし、個別のファイル内の SVG は有効な XML である必要があります。また、XML の解析は HTML よりもはるかに厳格です。HTML 内の SVG スニペットをもう一度示します。

<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 つ目の追加は、いわゆる「固有の記号」です。<feColorMatrix> タグが要素の開閉の両方を示す斜線です。この最後の変更は実際には必要ありませんが(明示的な </feColorMatrix> 閉じタグを使用することもできます)、XML と HTML 内の SVG の両方がこの /> ショートカットをサポートしているため、このショートカットを使用するほうがよいでしょう。

これらの変更により、このファイルを有効な 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 フィルタの機能をすべて利用できます。YouTube は Blink レンダラ機能を実装していますが、ウェブ プラットフォームを活用して実装しています。

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

エッジケースを除き、かなりの進歩を遂げました。同じドキュメント内にインライン <svg> が存在する必要がなくなったため、ソリューションは単一の自己完結型 CSS filter プロパティ定義にまで効果的に削減されました。これで、次に、この部分も削除しましょう。

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

ここまでの内容をまとめると次のようになります。

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

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

考えられた解決策の一つは、filter のように動作するが名前が異なる(--internal-devtools-filter など)新しい Chrome 内部 CSS プロパティを作成することです。次に、DevTools や DOM の計算スタイルにこのプロパティが表示されないようにする特別なロジックを追加します。必要な要素(ルート要素)でのみ機能するようにすることもできます。ただし、このソリューションは理想的ではありません。filter にすでに存在する機能を複製することになります。また、この非標準プロパティを隠そうとしても、ウェブ デベロッパーが見つけ出して使用し始める可能性があり、ウェブ プラットフォームにとって好ましくありません。DOM で検出されない方法で CSS スタイルを適用する必要があります。そのような方をご存じでしたら、紹介していただけないでしょうか。

CSS 仕様には、使用されるビジュアル フォーマット モデルを紹介するセクションがあり、その重要なコンセプトの 1 つがビューポートです。これは、ユーザーがウェブページを参照する視覚的なビューです。これに関連するコンセプトが、最初の包含ブロックです。これは、仕様レベルでのみ存在するスタイル設定可能なビューポート <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;
}

このコードがビューポート(より正確には、最初の包含ブロック)の z-indexdisplaypositionoverflow を処理していることを理解するために、C++ や Blink のスタイルエンジンの複雑さを理解する必要はありません。これらはすべて、CSS でよく見かけるコンセプトです。スタッキング コンテキストに関連するその他のマジックもあり、これらは CSS プロパティに直接変換されませんが、全体的には、この viewport オブジェクトは、DOM 要素と同様に Blink 内から CSS を使用してスタイル設定できるものと考えることができます。ただし、DOM の一部ではない点が異なります。

これで、まさに望む結果が得られます。filter スタイルを viewport オブジェクトに適用すると、レンダリングに視覚的に影響を与えることができます。このとき、測定可能なページ スタイルや DOM を妨げることはありません。

まとめ

ここまでの流れを簡単にまとめると、まず C++ ではなくウェブ技術を使用してプロトタイプを構築し、その一部を Blink レンダラーに移行する作業を開始しました。

  • まず、データの URL をインライン化することで、プロトタイプをより自己完結型にしました。
  • 次に、読み込みを特別なケースにすることで、これらの内部データ URL を CSP 対応にしました。
  • スタイルを Blink 内部の viewport に移動することで、実装を DOM に依存せず、プログラムで検出できないようにしました。

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

背景については、設計案または関連するすべてのパッチを参照している Chromium トラッキング バグをご覧ください。

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

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

Chrome DevTools チームに問い合わせる

次のオプションを使用して、DevTools の新機能、アップデート、その他のトピックについて話し合います。