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 ペイント ワークレット ファイルを読み込む必要があります。このファイルで、registerPaint 関数を使用してペイント ワークレット クラスを登録できます。

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

registerPaint('myPainter', MyPainter);

paint() コールバック内で、<canvas> で使用した CanvasRenderingContext2D と同じように ctx を使用できます。<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> を使用したことがある場合は、このコードに馴染みがあるはずです。こちらのライブデモをご覧ください。

背景画像としてチェック柄のパターンを使用した Textarea
チェッカーボード パターンが背景画像として使用されるテキストエリア。

一般的な背景画像を使用する場合との違いは、ユーザーが textarea のサイズを変更するたびに、パターンがオンデマンドで再描画されることです。つまり、高密度ディスプレイの補正を含め、背景画像は常に必要な大きさに正確に調整されます。

素晴らしいですが、静的でもあります。同じパターンでサイズの異なる正方形が必要な場合は、毎回新しいワークレットを作成する必要がありますか?答えは「いいえ」です。

ワークレットのパラメータ化

幸い、ペイント ワークレットは他の 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 の 2 か所でコードを調整する必要があります。

JS でペイント ワークレットのサポートを検出するには、CSS オブジェクトを確認します。 js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } CSS 側には、次の 2 つのオプションがあります。@supports は次のように使用できます。

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

より簡潔な方法は、不明な関数がある場合、CSS がプロパティ宣言を無効にし、その後でその宣言全体を無視するという事実を利用することです。プロパティを 2 回指定する場合(最初はペイント ワークレットなしで、次にペイント ワークレットありで指定する場合)は、段階的な拡張が適用されます。

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

ペイント ワークレットをサポートしているブラウザでは、background-image の 2 つ目の宣言が 1 つ目の宣言を上書きします。ペイント ワークレットをサポートしていないブラウザでは、2 番目の宣言は無効になり破棄され、1 番目の宣言が有効になります。

CSS ペイント ポリフィル

多くの用途では、CSS Paint Polyfill を使用することもできます。これにより、最新のブラウザに CSS カスタム ペイントとペイント ワークレットのサポートが追加されます。

ユースケース

ペイント ワークレットには多くのユースケースがあり、そのうちのいくつかは他よりも明白です。最もわかりやすい方法の 1 つは、ペイント ワークレットを使用して DOM のサイズを小さくすることです。多くの場合、要素は CSS を使用して装飾を作成するために追加されます。たとえば、マテリアル デザイン ライトでは、波紋効果のあるボタンに、波紋自体を実装するための <span> 要素が 2 つ追加されています。ボタンの数が多い場合、DOM 要素が多くなり、モバイルでのパフォーマンスが低下する可能性があります。代わりにペイント ワークレットを使用してリップル効果を実装すると、追加の要素は 0 個で、ペイント ワークレットは 1 つだけになります。また、カスタマイズとパラメータ化がはるかに簡単になります。

ペイント ワークレットを使用するもう 1 つの利点は、ほとんどの場合、ペイント ワークレットを使用したソリューションはバイト数が小さいことです。もちろんトレードオフはあります。キャンバスのサイズやパラメータが変更されるたびにペイント コードが実行されるということです。そのため、コードが複雑で時間がかかる場合、ジャンクが発生する可能性があります。Chrome は、長時間実行されるペイント ワークレットでもメインスレッドの応答性に影響を与えないように、ペイント ワークレットをメインスレッドから移動する作業を進めています。

私にとって最もエキサイティングな展望は、ペイント ワークレットによって、ブラウザにまだない CSS 機能を効率的にポリフィルできることです。たとえば、円錐形グラデーションが Chrome にネイティブに実装されるまで、ポリフィルで代用するなどです。もう一つの例は、CSS の会議で、枠線の色を複数設定できるようになったことです。会議がまだ始まっている間に、同僚の Ian Kilpatrick がペイント ワークレットを使用してこの新しい CSS 動作のポリフィルを作成しました。

既成概念にとらわれない考え方

背景画像や枠線の画像は、ペイント ワークレットについて学ぶとよくわかります。ペイント ワークレットの直感的でないユースケースの 1 つは、DOM 要素に任意の形状を持たせる mask-image です。たとえば、ダイヤモンド:

菱形の DOM 要素。
ダイヤモンドの形をした DOM 要素。

mask-image は、要素のサイズの画像を受け取ります。マスク画像が透明な領域、要素は透明です。マスク画像が不透明な領域、要素が不透明。

Chrome で利用可能に

ペイント ワークレットは、しばらくの間 Chrome Canary に存在していました。Chrome 65 では、デフォルトで有効になっています。ペイント ワークレットが提供する新しい可能性をお試しいただき、作成した作品をお見せください。さらにヒントが必要な場合は、Vincent De Oliveira のコレクションをご覧ください。