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

Mathias Bynens
Mathias Bynens

本文將說明我們如何在開發人員工具和 Blink Renderer 中實作色彩視覺缺陷模擬,以及實作方式。

背景:色彩對比不佳

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

網路上常見的無障礙功能問題清單。目前最常見的問題是低對比度的文字。

根據 WebAIM 針對前 100 萬個熱門網站提供的無障礙設計分析,超過 86% 的首頁對比度偏低。每個首頁平均會有 36 個不同的低對比文字。

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

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

我們在這份清單中最近加入了一項新工具,與其他工具略有不同。上述工具著重於顯示對比度資訊,並提供相關選項來修正。我們發現開發人員工具仍缺少一項工具,可讓開發人員深入瞭解這個問題領域。為解決這個問題,我們在開發人員工具「轉譯」分頁中實作視覺延遲模擬功能。

在 Puppeteer 中,新版 page.emulateVisionDeficiency(type) API 可讓您透過程式輔助方式啟用這些模擬作業。

色彩視覺障礙

約有 1/20 人受到色覺障礙 (也稱為「色盲」) 影響。因為這類障礙會讓使用者更難以分辨不同顏色,進而放大對比問題

繽紛的蠟筆相片,未模擬色盲情形
融化的彩色蠟筆相片,影像沒有模擬色彩障礙效果。
ALT_TEXT_HERE
在色彩繽紛的蠟筆相片上,模擬全營養素的影響。
在色彩繽紛的蠟筆相片中模擬綠色細菌的影響。
在色彩繽紛的蠟筆相片中模擬綠色細菌的影響。
模擬紅色細胞對融化蠟筆的色彩影響。
模擬紅色細胞對融化蠟筆的色彩影響。
在色彩繽紛的蠟筆相片上模擬藍色細菌帶來的影響。
在色彩繽紛的蠟筆相片上模擬藍色細菌帶來的影響。

身為使用一般視覺的開發人員,開發人員工具中的色彩組合在視覺上可以看得見時,可能會顯示不正確的對比度。因為對比度公式會考慮這些色彩視覺不足之處!在某些情況下,你可能還是能夠閱讀低對比度的文字,但視障者並不具備這項權限。

我們希望讓設計人員和開發人員模擬這些視覺缺陷對其網路應用程式的影響,提供缺少的部分:開發人員工具不僅有助於「尋找」及「修正」對比問題,現在您也能瞭解這些問題!

使用 HTML、CSS、SVG 和 C++ 模擬色彩視覺障礙

在深入介紹 Blink Renderer 的功能實作前,建議您先瞭解如何使用網頁技術實作對等的功能。

您可以將每種色覺障礙模擬視為涵蓋整個頁面的重疊影像。Web Platform 的用途是執行 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 的物理視覺弱勢模擬模型為基礎。

總之,我們有了這個 SVG 濾鏡,現在只要使用 CSS,就能套用到網頁上的任意元素。我們可以針對其他視覺缺陷重複相同的模式。範例如下:

如有需要,我們可以按照下列方式建構開發人員工具功能:當使用者在開發人員工具 UI 中模擬視覺障礙時,就會在檢查過的文件中插入 SVG 濾鏡,然後對根元素套用濾鏡樣式。但這種方法有幾個問題:

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

除了邊緣情況,這種做法的主要問題在於以程式輔助方式對頁面進行可觀測的變更。如果開發人員工具使用者檢查 DOM,可能會突然看到從未新增的 <svg> 元素,或是從未寫入的 CSS filter。這只會讓人困惑!如要在開發人員工具中實作這項功能,我們需要有沒有這些缺點的解決方案。

接著來看如何降低使用者的干擾。這個解決方案有兩個部分,需要隱藏: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 部分開始:如何避免將可擴充向量圖形加到 DOM?我們建議將其移至獨立的 SVG 檔案。我們可以複製上述 HTML 中的 <svg>…</svg>,並將其儲存為 filter.svg,但我們必須先做一些變更!根據 HTML 剖析規則,在 HTML 中內嵌 SVG。也就是說,即使在某些情況下,在屬性值前後省略引號這類的指令也沒關係。不過,獨立檔案中的 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>

為使這個有效的獨立可擴充向量圖形 (以及 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 和 SVG-in-HTML 都支援這項機制,/>我們或許也能善加利用。

總之,完成變更後,我們最終可以將這個檔案儲存為有效的 SVG 檔案,並指向 HTML 文件中的 CSS filter 屬性值:

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

哇,我們再也不必在文件中插入 SVG!這樣好多了。但是...現在我們依附另一個檔案。這仍為依附元件。我們可以設法擺脫它嗎?

結果後,我們其實不需要檔案。我們可以使用資料網址,將整個檔案編碼為一個網址。為此,我們只會擷取先前使用 SVG 檔案的內容、新增 data: 前置字串、設定適當的 MIME 類型,接著我們便得到一個代表同一個 SVG 檔案的有效資料網址:

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 Renderer 的最終實作方式其實非常相似。我們新增了以下 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 "";
  }
}

請注意,這項技術讓我們能完整發揮可擴充向量圖形濾鏡的功能,不需要重新實作任何項目或重新定義任何輪子。我們正在導入 Blink Renderer 功能,但目前採用的是 Web Platform。

好的,我們已經瞭解如何建立 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 Platform 來說是壞事。我們需要其他套用 CSS 樣式的方法,但是無法在 DOM 中觀察。有什麼好辦法嗎?

CSS 規格部分介紹了所使用的視覺格式模型,而可視區域包含其中一個重要概念。使用者透過這種視覺化檢視畫面來查詢網頁。所謂的「初始包含區塊」就是密切相關的概念,這是一種僅存在於規格層級的可設定可視區域 <div>。規格所指的其實是整個地點的「可視區域」概念。舉例來說,如果內容不適合,你知道瀏覽器如何顯示捲軸嗎?這些都是根據這個「可視區域」在 CSS 規格中定義。

這個 viewport 也存在於 Blink Renderer 中,以及實作詳細資料。以下程式碼會根據規格套用預設的可視區域樣式:

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 物件視為 Blink 內的 CSS (如同 DOM 元素,但並非 DOM 的一部分) 可使用的樣式。

這正是我們需要的!我們可以將 filter 樣式套用到 viewport 物件,這個樣式會在不影響算繪的情況下影響算繪,而且不會以任何方式乾擾可觀察的網頁樣式或 DOM。

結論

在此回顧一下這堂課的重點,我們先使用網頁技術 (而不是 C++) 來建構原型,然後開始將部分原型移至 Blink Renderer。

  • 我們首先透過內嵌資料網址,讓原型更加獨立。
  • 接著,我們特別在載入內容時特別留意,讓內部資料網址支援 CSP。
  • 我們將樣式移至 Blink-internal viewport,使實作 DOM 適用且以程式輔助方式觀測。

這項導入作業的特別之處在於,我們的 HTML/CSS/SVG 原型設計最終會影響最終的技術設計。我們發現 Web Platform 的應用方式,即使是在 Blink Renderer 中也是如此!

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

下載預覽頻道

建議您使用 Chrome CanaryDevBeta 版做為預設的開發瀏覽器。透過這些預覽版本,您可以存取開發人員工具中的最新功能、測試最先進的網路平台 API,以及找出網站的問題,以免使用者發現問題。

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

請使用下列選項,討論貼文中的新功能和異動,或與開發人員工具相關的其他事項。

  • 歡迎透過 crbug.com 提出建議或意見。
  • 使用「更多選項」更多 > 回報開發人員工具問題說明 >在開發人員工具中回報開發人員工具問題
  • 前往 @ChromeDevTools 張貼 Tweet。
  • 歡迎在「開發人員工具」推出「最新消息」YouTube 影片或「開發人員工具秘訣」YouTube 影片留言。