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

この例の正確な数値はどこから来るのか、疑問に思われるかもしれません。このカラー マトリックスがデューテロノピーを近似している理由は何ですか?答えは科学です!これらの値は、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 と 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 フィルタの機能をすべて利用できます。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 で計算されたスタイルを調べたときにも表示されるため、混乱を招く可能性があります。このような問題を回避するにはどうすればよいですか?デベロッパーがプログラムで監視できないように、ドキュメントにフィルタを追加する方法を見つける必要があります。

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

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

このコードがビューポート(より正確には、最初の包含ブロック)の 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 の新機能、更新、その他のトピックについて話し合います。