CSS Paint API

Chrome 65 的新功能

自 Chrome 65 版起,系統預設會啟用 CSS Paint API (又稱為「CSS Custom Paint」或「Houdini 的顏料)」。這是什麼?你可以如何運用?運作方式為何?繼續閱讀吧...

有了 CSS Paint API,只要 CSS 屬性預期圖片,即可透過程式輔助方式產生圖片。background-imageborder-image 等屬性通常會與 url() 搭配使用,以載入圖片檔,或與 linear-gradient() 等 CSS 內建函式搭配使用。您現在可以用 paint(myPainter) 來參照繪製工作列,而不用使用這些符號。

撰寫油漆工

如要定義名為 myPainter 的繪製工作列,我們必須使用 CSS.paintWorklet.addModule('my-paint-worklet.js') 載入 CSS Paint Worklet 檔案。在這個檔案中,我們可以使用 registerPaint 函式註冊 Paint Worklet 類別:

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

registerPaint('myPainter', MyPainter);

paint() 回呼中,我們可以使用 ctx 的方式與從 <canvas> 中得知的 CanvasRenderingContext2D 相同。如果您知道如何在 <canvas> 中繪製,即可在繪製工作小程式中繪圖!geometry 會指出我們處置的畫布寬度和高度。properties我稍後會在本文中說明這一點。

做為入門範例,我們要編寫一個棋盤繪製工作小程式,然後用它做為 <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>,那麼這個程式碼應該不陌生。請按這裡觀看直播示範。

以棋盤圖案做為背景圖片的文字區域
具有棋盤模式做為背景圖片的 Textarea。

這裡使用一般背景圖片的差異在於,每當使用者調整文字區域大小,系統都會視需要重新繪製模式。這表示背景圖片的大小一律符合需求,其中包括針對高密度螢幕的補償。

這是很酷的,但也非常靜態。每次想要相同的模式,但大小不同的正方形時,是否都想要寫入新的工作?答案是不會!

將 Worklet 參數化

幸好, Paintlet 可以存取其他 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);

現在,我們可以針對各種類型的檢查板使用相同的程式碼。不過更棒的是,現在可以進入開發人員工具並比對值,直到找到正確的樣式為止。

不支援漆彈的瀏覽器

本文撰寫時,只有 Chrome 實作了漆工。雖然其他瀏覽器供應商都有正面信號,但進度不多。若要掌握最新資訊,請定期查看「Is Houdini Ready't?」(是否已準備好了嗎?)。在此期間,即使不支援繪製小程式,仍請務必使用漸進式的強化功能,讓程式碼能夠持續運作。為確保運作順利,您必須在兩個地方調整程式碼: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 繪製 Polyfill

有許多用途也能使用 CSS 繪製 Polyfill,讓新式瀏覽器支援 CSS 自訂繪製和繪製工具。

應用情境

彩繪工作有許多用途,其中一些技巧也比其他工具更明顯。另一個比較明顯的做法是使用繪製工作小程式縮減 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 中取得

Chrome Canary 已有一段時間推出塗鴉工具。在 Chrome 65 中,這項功能預設為啟用。請大膽嘗試各種新可能性 看一下您做的!更多靈感請見 Vincent De Oliveira 系列影片