在 Blink Renderer 中模擬色彩視覺不足

Mathias Bynens
Mathias Bynens

本文將說明我們在 DevTools 和 Blink 轉譯器中實作色盲模擬功能的原因和方式。

背景:色彩對比不佳

低對比文字是網路上最常見的自動偵測無障礙功能問題。

列出網站上常見的無障礙問題。低對比度文字是目前最常見的問題。

根據 WebAIM 對百萬大網站的無障礙分析,超過 86% 的首頁對比度偏低。平均來說,每個首頁都有 36 個獨立的低對比文字

使用開發人員工具找出、瞭解及修正對比問題

Chrome 開發人員工具可協助開發人員和設計人員改善對比度,並為網頁應用程式挑選更容易存取的色彩配置:

我們最近在這個清單中新增了一個工具,與其他工具略有不同。上述工具主要用於顯示對比率資訊,並提供修正對比率的選項。我們發現 DevTools 仍缺少一種方法,讓開發人員能更深入瞭解這個問題領域。為解決這個問題,我們在開發人員工具的「算繪」分頁中實作了視覺障礙模擬功能。

在 Puppeteer 中,新的 page.emulateVisionDeficiency(type) API 可讓您以程式輔助方式啟用這些模擬功能。

色覺辨認障礙

大約每 20 人中就有 1 人會出現色覺辨認障礙 (也稱為「色盲」,但這個詞彙不太準確)。這類障礙會讓使用者更難分辨不同顏色,進而放大對比度問題

彩色蠟筆融化後的圖片,沒有模擬色彩視覺缺陷
彩色融化的蠟筆相片,沒有模擬色覺辨認障礙。
ALT_TEXT_HERE
模擬全色盲對彩色蠟筆融化圖片的影響。
模擬對綠色失能症的影響,在融化的蠟筆彩色圖片上顯示。
模擬第二色盲對融蠟筆的彩色圖片的影響。
模擬色盲對彩色蠟筆融化圖片的影響。
模擬紅色盲對彩色蠟筆融化圖片的影響。
模擬三色盲對融蠟筆彩色圖片的影響。
模擬三色盲對融蠟筆彩色圖片的影響。

身為視力正常的開發人員,您可能會發現開發人員工具針對視覺上看起來沒問題的顏色組合,顯示不良的對比度。這是因為對比度公式會考量這些色彩視力缺陷!在某些情況下,仍可閱讀低對比文字,但視障人士就沒有這個優勢。

我們希望能提供缺少的部分,讓設計師和開發人員模擬這些視覺障礙對自家網頁應用程式的影響:除了透過開發人員工具找出修正對比度問題,現在您還能瞭解這些問題!

使用 HTML、CSS、SVG 和 C++ 模擬色覺辨認障礙

在深入探討 Blink 轉譯器的功能實作方式前,建議您先瞭解如何使用網路技術實作等同的功能。

您可以將每個色覺障礙模擬視為覆蓋整個頁面的疊加層。Web 平台有一種方法可以做到這點:CSS 濾鏡!您可以使用 CSS filter 屬性,使用一些預先定義的篩選器函式,例如 blurcontrastgrayscalehue-rotate 等等。如要進一步控制,filter 屬性也接受可指向自訂 SVG 濾鏡定義的網址:

<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 的乘數 (從左到右),以及常數位移值的第五個值。矩陣有 4 列:矩陣的第一列用於計算新的紅色值、第二列綠色、第三列藍色,以及最後一列 Alpha。

您可能會好奇,範例中的確切數字是從何而來。為什麼這個色彩矩陣能很好地模擬綠色盲?答案是:科學!這些值是根據 Machado、Oliveira 和 Fernandes 所提供的色覺缺陷模擬模型,以生理學角度精確模擬而得。

無論如何,我們有這個可擴充向量圖形濾鏡,現在可以使用 CSS 將其套用至網頁上的任意元素。我們可以針對其他視力障礙重複相同的模式。以下是示範:

我們可以按照以下方式建構 DevTools 功能:當使用者在 DevTools UI 中模擬視力障礙時,我們會將 SVG 濾鏡插入檢查的文件中,然後在根元素上套用濾鏡樣式。不過,這種做法有幾個問題:

  • 頁面可能已在其根元素上加入篩選器,而我們的程式碼可能會覆寫該篩選器。
  • 頁面可能已包含 id="deuteranopia" 元素,與篩選器定義衝突。
  • 網頁可能會依賴特定 DOM 結構,如果將 <svg> 插入 DOM,可能會違反這些假設。

除了極端情況外,這種做法的主要問題是我們會對網頁進行可透過程式碼觀察到的變更。如果 DevTools 使用者檢查 DOM,可能會突然看到自己從未新增的 <svg> 元素,或是從未編寫的 CSS filter。這樣會造成混淆!如要在開發人員工具中實作這項功能,我們需要一個沒有這些缺點的解決方案。

讓我們來看看如何讓這項功能不那麼令人分心。這個解決方案有兩個部分需要隱藏:1) 使用 filter 屬性的 CSS 樣式,以及 2) 目前屬於 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 中的內嵌 SVG 會遵循 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>

為了讓這個可擴充向量圖形 (以及 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 命名空間宣告。第二個新增項目是所謂的「斜線」:斜線表示 <feColorMatrix> 標記會同時開啟和關閉元素。這項最後的變更其實並非必要 (我們可以改用明確的 </feColorMatrix> 結束標記),但由於 XML 和 HTML 中的 SVG 都支援這個 /> 速記字元,因此我們還是可以使用它。

無論如何,在完成這些變更後,我們終於可以將這個圖片儲存為有效的 SVG 檔案,並在 HTML 文件的 CSS filter 屬性值中指向該檔案:

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

太好了,我們現在不必再將 SVG 插入文件!這樣就好多了。但我們現在需要使用其他檔案。這仍是依附元件。我們可以以某種方式移除嗎?

事實上,我們並不需要檔案。我們可以使用資料網址,在網址中編碼整個檔案。為此,我們會直接取用先前的可擴充向量圖形檔案內容,然後加入 data: 前置字串,並設定適當的 MIME 類型,這樣我們就能取得代表相同可擴充向量圖形檔案的有效資料網址:

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 文件中使用該檔案。因此,我們現在可以指向資料網址,而非像先前那樣參照檔案名稱:

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

在網址結尾,我們仍會像先前一樣指定要使用的篩選器 ID。請注意,您不需要在網址中對 SVG 文件進行 Base64 編碼,這麼做只會降低可讀性並增加檔案大小。我們在每行結尾處加入反斜線,確保資料網址中的換行字元不會終止 CSS 字串文字常值。

到目前為止,我們只討論了如何使用網路技術模擬視力障礙。有趣的是,我們在 Blink 轉譯器中最終實作的內容其實非常相似。以下是我們新增的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 屬性值中使用的資料網址。您是否能想到這項技術的問題?事實上,我們無法在所有情況下依賴載入的資料網址,因為目標網頁可能含有會封鎖資料網址的Content-Security-Policy。我們最終在 Blink 層級實作時,特別小心在載入期間為這些「內部」資料網址略過 CSP。

除了極端情況外,我們已取得不錯的進展。由於我們不再依賴同一個文件中的內嵌 <svg>,因此解決方案實際上只剩下單一自給自足的 CSS filter 屬性定義。太好了!我們現在也來移除這個元素。

避免文件內 CSS 相依性

重點回顧:

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

我們仍依賴這個 CSS filter 屬性,這可能會覆寫實際文件中的 filter,並導致問題。在開發人員工具中檢查已計算的樣式時,也會顯示這項資訊,這會造成混淆。如何避免這些問題?我們需要找到一種方法,在文件中新增篩選器,但不讓開發人員以程式設計方式觀察到該篩選器。

我們想到的其中一個方法,是建立新的 Chrome 內部 CSS 屬性,讓其運作方式與 filter 類似,但名稱不同,例如 --internal-devtools-filter。接著,我們可以新增特殊邏輯,確保這個屬性不會顯示在開發人員工具或 DOM 中所計算的樣式中。我們甚至可以確保它只會套用於所需的一個元素:根元素。不過,這個解決方案並不理想:我們會複製 filter 中現有的功能,而且即使我們盡力隱藏這個非標準屬性,網頁開發人員還是可以找到並開始使用它,這對 Web 平台不利。我們需要其他方法來套用 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 樣式引擎的複雜性,即可瞭解這個程式碼會處理 viewport (或更準確地說,初始包含區塊) 的 z-indexdisplaypositionoverflow。這些都是您可能熟悉的 CSS 概念!還有一些與堆疊內容相關的魔法,這些魔法不會直接轉譯為 CSS 屬性,但整體來說,您可以將這個 viewport 物件視為可在 Blink 中使用 CSS 設定樣式的物件,就像 DOM 元素一樣,只是它不是 DOM 的一部分。

這正是我們想要的!我們可以將 filter 樣式套用至 viewport 物件,這會在視覺上影響轉譯,但不會以任何方式干擾可觀察的網頁樣式或 DOM。

結論

回顧這段旅程,我們一開始是使用網路技術 (而非 C++) 建構原型,然後開始將部分原型移至 Blink 轉譯器。

  • 我們首先透過內嵌資料網址,讓原型設計更為自給自足。
  • 接著,我們透過特殊載入方式,讓這些內部資料網址符合 CSP 規範。
  • 我們將實作項目移至 Blink 內部 viewport,讓實作項目不受 DOM 影響,並且在程式碼中不可觀察。

這項實作方式的獨特之處在於,HTML/CSS/SVG 原型設計最終影響了技術設計。我們找到了一種方法,即使在 Blink 轉譯器中也能使用 Web 平台!

如需更多背景資訊,請參閱我們的設計提案Chromium 追蹤錯誤,其中列出所有相關修補程式。

下載預覽管道

建議您將 Chrome Canary開發人員版Beta 版設為預設開發人員版瀏覽器。這些預覽管道可讓您存取最新的 DevTools 功能,測試最新的網路平台 API,並在使用者發現問題前,協助您找出網站的問題!

與 Chrome 開發人員工具團隊聯絡

請使用下列選項討論新功能、更新或任何與開發人員工具相關的內容。