CSS Paint API

Chrome 65 新增無限可能

從 Chrome 65 開始,系統會預設啟用 CSS Paint API (也稱為「CSS 自訂繪圖」或「Houdini 的繪圖工作項」)。這是什麼?您可以如何運用?運作方式為何?繼續往下看吧...」

只要 CSS 屬性需要圖片,CSS Paint API 就能透過程式輔助產生圖片。background-imageborder-image 等屬性通常會與 url() 搭配使用,用於載入圖片檔案,或與 linear-gradient() 等 CSS 內建函式搭配使用。您現在可以改用 paint(myPainter) 來參照繪圖工作區,而非使用上述方法。

編寫繪圖工作區

如要定義名為 myPainter 的繪圖工作區,我們需要使用 CSS.paintWorklet.addModule('my-paint-worklet.js') 載入 CSS 繪圖工作區檔案。在該檔案中,我們可以使用 registerPaint 函式註冊繪圖工作區類別:

class MyPainter {
  paint(ctx, geometry, properties) {
    // ...
  }
}

registerPaint('myPainter', MyPainter);

paint() 回呼中,我們可以使用 ctx,就像我們在 <canvas> 中使用 CanvasRenderingContext2D 一樣。如果您知道如何在 <canvas> 中繪圖,那麼您也可以在繪圖工作區中繪圖!geometry 會告訴我們可用的畫布寬度和高度。properties 我會在本文稍後說明。

我們先透過簡單的範例,瞭解如何編寫格狀圖形繪圖工作區,並將其用於 <textarea> 的背景圖片。(我使用 textarea,因為它預設可調整大小):

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  paint(ctx, geom, properties) {
    // Use `ctx` as if it was a normal canvas
    const colors = ['red', 'green', 'blue'];
    const size = 32;
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        const color = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.fillStyle = color;
        ctx.rect(x * size, y * size, size, size);
        ctx.fill();
      }
    }
  }
}

// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);

如果您過去曾使用 <canvas>,應該對這段程式碼很熟悉。請前往這裡查看現場示範。

使用格狀圖案做為背景圖片的文字方塊
使用棋盤格圖案做為背景圖片的文字區域。

這裡使用常見的背景圖片的不同之處在於,每當使用者調整文字區域大小時,模式就會隨選重新繪製。這表示背景圖片一律會維持所需大小,包括為高密度螢幕提供的補償。

很酷,但也很靜態。我們是否希望每次想要使用相同圖案但不同大小的方塊時,都需要編寫新的 worklet?答案是不會!

對工作單元設定參數

幸運的是,繪圖工作區塊可以存取其他 CSS 屬性,這就是額外參數 properties 發揮作用的地方。只要為類別提供靜態 inputProperties 屬性,即可訂閱任何 CSS 屬性 (包括自訂屬性) 的變更。系統會透過 properties 參數提供這些值。

<!-- index.html -->
<!doctype html>
<style>
  textarea {
    /* The paint worklet subscribes to changes of these custom properties. */
    --checkerboard-spacing: 10;
    --checkerboard-size: 32;
    background-image: paint(checkerboard);
  }
</style>
<textarea></textarea>
<script>
  CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
  // inputProperties returns a list of CSS properties that this paint function gets access to
  static get inputProperties() { return ['--checkerboard-spacing', '--checkerboard-size']; }

  paint(ctx, geom, properties) {
    // Paint worklet uses CSS Typed OM to model the input values.
    // As of now, they are mostly wrappers around strings,
    // but will be augmented to hold more accessible data over time.
    const size = parseInt(properties.get('--checkerboard-size').toString());
    const spacing = parseInt(properties.get('--checkerboard-spacing').toString());
    const colors = ['red', 'green', 'blue'];
    for(let y = 0; y < geom.height/size; y++) {
      for(let x = 0; x < geom.width/size; x++) {
        ctx.fillStyle = colors[(x + y) % colors.length];
        ctx.beginPath();
        ctx.rect(x*(size + spacing), y*(size + spacing), size, size);
        ctx.fill();
      }
    }
  }
}

registerPaint('checkerboard', CheckerboardPainter);

我們現在可以使用相同的程式碼,為所有不同類型的棋盤格式產生圖像。更棒的是,我們現在可以進入 DevTools,調整值,直到找到正確的外觀。

不支援繪圖工作區的瀏覽器

在撰寫本文時,只有 Chrome 實作了繪圖工作單元。雖然所有其他瀏覽器供應商都提供正面信號,但進展不大。如要隨時掌握最新消息,請定期查看「Houdini 是否已就緒?」在此同時,請務必使用漸進式增強功能,即使不支援繪圖工作區塊,也能讓程式碼持續執行。為確保一切正常運作,您必須在兩個地方調整程式碼:CSS 和 JS。

您可以檢查 CSS 物件,藉此偵測 JS 中是否支援繪圖工作區: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } CSS 方面有兩種做法。您可以使用 @supports

@supports (background: paint(id)) {
  /* ... */
}

更精簡的技巧是利用 CSS 會在屬性宣告中出現不明函式時,將整個屬性宣告視為無效並忽略這項事實。如果您兩次指定屬性 (第一次不含繪圖工作區,第二次則含繪圖工作區),就會獲得漸進式增強功能:

textarea {
  background-image: linear-gradient(0, red, blue);
  background-image: paint(myGradient, red, blue);
}

支援繪圖工作區的瀏覽器中,第二個 background-image 宣告會覆寫第一個宣告。在不支援繪圖工作區的瀏覽器中,第二個宣告無效,並且會遭到捨棄,讓第一個宣告生效。

CSS 繪圖填充

在許多情況下,您也可以使用 CSS Paint Polyfill,為新式瀏覽器新增 CSS 自訂 Paint 和 Paint Worklet 支援。

用途

繪圖工作項的用途相當多元,其中有些用途相當明顯。其中一個較明顯的做法是使用繪圖工作區,縮減 DOM 的大小。有時會加入元素,純粹是為了使用 CSS 製作修飾圖案。舉例來說,在 Material Design Lite 中,漣漪效果按鈕包含 2 個用於實作漣漪效果的額外 <span> 元素。如果您有許多按鈕,這可能會增加 DOM 元素的數量,並導致行動裝置效能降低。如果您改為使用繪圖工作區塊實作漣漪效果,最終只會產生 0 個額外元素,以及一個繪圖工作區塊。此外,您可以更輕鬆地自訂及設定參數。

使用顏料工作的另一個好處是,在大部分情況下,使用顏料工作的解決方案是少量位元組的解決方案。當然,這麼做有其代價:每當畫布的大小或任何參數發生變更時,繪圖程式碼就會執行。因此,如果程式碼複雜且耗時,可能會導致卡頓。Chrome 正在努力將繪圖工作項移出主執行緒,這樣即使繪圖工作項長時間執行,也不會影響主執行緒的回應速度。

對我來說,最令人期待的前景是,繪圖工作區塊可讓瀏覽器針對尚未提供的 CSS 功能,進行有效的半透明處理。舉例來說,您可以使用 polyfill 為 圓錐漸層提供支援,直到 Chrome 原生支援這類效果為止。另一個例子:在 CSS 會議中,我們決定現在可以使用多種邊框顏色。在這個會議進行期間,我的同事 Ian Kilpatrick 使用繪圖工作區塊,為這項新的 CSS 行為編寫了 polyfill

跳脫框架思考

大多數人學習繪圖工作區時,都會開始思考背景圖片和邊框圖片。繪圖工作區塊的一個較不直覺的用途是 mask-image,可讓 DOM 元素具有任意形狀。例如鑽石

鑽石形狀的 DOM 元素。
鑽石形狀的 DOM 元素。

mask-image 會擷取與元素大小相同的圖片。遮罩圖片透明的區域,元素就會是透明的。遮罩圖片不透明的區域 (元素不透明)。

現已在 Chrome 中推出

Paint 工作區塊已在 Chrome Canary 中運作一段時間。在 Chrome 65 中,這項功能預設為啟用。快試試看畫圈的新可能性 看看你的成果吧!如需更多靈感,請參考 Vincent De Oliveira 的收藏