CSS Paint API

Chrome 65 の新機能

CSS Paint API(「CSS カスタム ペイント」または「Houdini のペイント ワークレット」とも呼ばれます)は、Chrome 65 以降ではデフォルトで有効になっています。概要: 何ができますか?仕組みでは、続きをどうぞ。

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 のみです。他のすべてのブラウザ ベンダーから前向きなシグナルはありますが、進展はほとんどありません。最新情報については、Is Houdini Ready Yet? を定期的にご確認ください。それまでは、ペイント ワークレットがサポートされていない場合でもコードを実行し続けられるように、段階的な拡張機能を使用してください。想定どおりに動作するようにするには、CSS と JS の 2 か所でコードを調整する必要があります。

JavaScript でペイント ワークレットのサポートを検出するには、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 のコレクションをご覧ください。