ペイントクラスの定義と登録
CSS paingint APIを用いたペイント処理はペイントワークレットモジュール内のステートレスなペイントクラスとして定義します. ペイントクラスの構成要件は次のとおりです.
ペイントクラスが実装すべきAPI
必須 | 関数/プロパティ | 引数の内容/戻り値の意味合い | 役割 |
○ | paint(ctx, size, properties, args) |
ctx:描画処理を施す対象となるコンテキストオブジェクト
size:ノードの描画サイズ
properties:ノードの現行スタイル (inputPropertiesで指定した内容)
args:paint関数に渡した引数の内容 (inputArgumentsで指定した内容) | ペイント処理を行います. |
- | static get contextOptions() | {alpha: true}
trueが初期値, falseでアルファ値を使わない(初期グラフィックが黒) | 描画コンテキストの初期設定パラメータを指定します. |
- | static get inputProperties() | ["プロパティのリスト"] | ペイント処理に必要なCSSプロパティ名のリスト(CSS変数名を含む)を指定します. |
- | static get inputArguments() | ["<CSSデータ型のリスト>"]
length, number, percentage, length-percentage, color, image, url, integer, angle, time, resolution, transform-list, custom-ident | paint関数に渡すべきパラメータの型を指定します. |
ペイントクラスのインスタンスの生存期間はWEBブラウザ側で管理されます. また同時に複数のクラスが同時並行的に処理を行うことがあるため, グローバル領域の変数を書き換えると言った操作は行なえません. |
作成したペイントクラスはregisterPaintメソッドでWEBページに登録します. 登録したペイントクラスはWEBページの再描画が必要となった際に自動的に呼び出されます. なお, ペイントクラスは同時並行的に呼び出されることがあるため, グローバル領域に対して値を格納/変更すると言った操作を行ってはなりません.
- paintWorkletGlobalScope.registerPaint(id, paintCtor)
- 指定したidでペイント処理を実装したペイントクラス(ペイントコンストラクタ)をWEBページに登録します. 登録したペイント処理はCSS側から呼び出すことが可能です.
ペイントクラス作成上の条件
registerPaintメソッドに渡すペイントクラスは少なくともpaintメソッドを実装している必要があります.
なおペイントクラスの定義に従来のコンストラクタ関数(new演算子を使ってインスタンス化できるもの)を用いることも可能です.
ペイントクラスの継承
ペイントクラスは一般のJavaScriptクラスですから, 親となるクラスにペイントクラス共通の処理を定義しておくことができます.
また, 静的プロパティ内ではsuperキーワードを使って子クラスから親クラスのAPIを参照することが可能です. この仕組みを使い, 親クラスでの設定に子クラス側で内容を追加することが出来ます.
ペイントid重複時の動作
registerPaintメソッドに指定したidが既に登録済みのものだった場合, (設定を上書きせず)エラーとなります. これは別のペイントワークレットモジュールを実行した際も同様です.
登録したペイント処理の呼び出し
registerPaintメソッドで登録したペイント処理はCSS側のpaint関数から呼び出すことが出来ます. paint関数が生成した画像は一般のラスタ画像等と同等に扱われ, background-positionといった描画処理の調整を行うプロパティの制御対象ともなります.
- [画像を扱うプロパティ]: paint(id [, arguments]?)
- ペイントワークレットモジュールで登録したペイント処理を呼び出します.
画像を扱うプロパティにはbackground-image, mask-image, border-image-source, list-style-image, shape-outside†等があります.
この他にも「画像」を扱うプロパティはありますが, paint関数が正しく動作するかは不明です.
幾つか例を示します.
ペイント処理のインスタンス生存期間
ペイントクラスのインスタンスはWEBブラウザにより必要となった時に自動的にロードされ, 不必要と判断された任意のタイミングでメモリから開放されます. そのため, ペイントクラスはステートレス(特定の状態を採らない)ように設計すべきです. インスタンスに独自のプロパティを定義したり(格納した値を後から正しく取得できる保証がない)インスタンスの内容を別途変数に保管することに余計なメモリを消費する以上の意味はありません.
補足)ペイントクラス設定の取得
paintメソッドから当該ペイントクラスの各種設定情報を取得するにはthis.__proto__.constructorにアクセスします.
コンテキストの初期化パラメータ
ペイントクラスのcontextOptionsプロパティにはコンテキストオブジェクトの初期化パラメータを設定します.
alpha:アルファ(不透明度)の設定
パラメータalphaはアルファ値の取扱いについて指定します. trueでアルファ値を有効とし, falseで無効とします. アルファ値を有効化した場合, 描画領域は黒の透明で, 無効化した場合は黒色で初期化されます.
ペイント処理の実装
実際のペイント処理はpaintメソッドの引数として渡されるコンテキスト(PaintRenderingContext2D)オブジェクトに対して行います. 例を示します.
PaintRenderingContext2Dオブジェクトの機能
PaintRenderingContext2Dオブジェクトはcanvas要素で用いられるCanvasRenderingContext2Dオブジェクトから派生したいわば機能制限版であり, 基本的な使い方に違いはありません. 以下に利用できるAPIについてまとめます.
PaintRenderingContext2Dで利用できるcanvasAPI
| カテゴリ | API |
利用可 | CanvasState | save, restore |
CanvasTransform | scale, rotate, translate, transform, getTransform |
CanvasCompositing | globalAlpha, globalCompositeOperation |
CanvasImageSmoothing | imageSmoothEnabled, imageSmoothingQuality |
CanvasFillStrokeStyles | strokeStyle, fillStyle, createLinearGradient, createRadialGradient, createPattern |
CanvasShadowStyles | shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor |
CanvasRect | clearRect, fillRect, strokeRect |
CanvasDrawPath | beginPath, fill, stroke, clip, resetClip, isPointInPath, isPointInStroke |
CanvasDrawImage | drawImage |
CanvasPathDrawingStyles | lineWidth, lineCap, lineJoin, miterLimit, setLineDash, getLineDash, lineDashOffset |
CanvasPath | closePath, moveTo, lineTo, quadraticCurveTo, bezierCurveTo, arcTo, rect, arc, ellipse |
利用 不可 | CanvasImageData(ピクセル操作), CanvasUserInterface(UI操作), CanvasText/CanvasTextDrawingStyles(テキストの描画) |
備考) 実装途上である現状では全てのAPIが利用できるとは限りません. |
なお, PaintRenderingContext2Dには描画済みのグラフィックにアクセスする手段が存在しません. つまり, 描いた内容を一時的に保管しておき後から書き戻すといった操作を行えません.
描画コンテキストの生成タイミング
PaintRenderingContext2Dオブジェクトはpaint関数を呼び出す都度生成されます. つまり, グラフィックの描画状態及びコンテキスト状態(fillStyleやclip領域の内容)は維持されません.
ペイント対象の判定
基本的にペイント処理内部からペイント対象のノードを知ることは出来ません. あくまでノードに設定されたスタイル情報からペイント処理を行うのみです.
エラー発生時の動作
ペイント処理内でエラーが発生した場合は, コンテキストオブジェクトに対する操作が無効となり, エラーの内容がコンソールに出力されます.
console.logメソッド実行上の注意
paintメソッドに渡される引数(PaintRenderingContext2D, PaintSize, CSSKeywordValue, CSSStyleValue)はpaintメソッドが呼び出される毎に生成されています. そのためこれらのオブジェクトをデバッグ目的でconsole.logメソッドの引数として渡した場合, コンソール上にオブジェクトへの参照が残り続けることでメモリリークの原因となります. 特にアニメーションを行う場合はpaintメソッドの呼び出しが頻発するため注意が必要です.
描画範囲の取得
paintメソッドの第2引数にはグラフィックの描画範囲を表すPaintSizeオブジェクトが渡されます.
- PaintSize.width
- グラフィックの(論理的な)描画範囲幅
- PaintSize.height
- グラフィックの(論理的な)描画範囲高
PaintSizeオブジェクトで得られる値は, ペイント処理が生成する画像の画素数ではなくノードの描画範囲から得られた論理的な描画サイズです. この内容を元にPaintRenderingContext2Dを操作するとズーム倍率やスクリーン画素の密度を意識することなく最適な†グラフィックが得られます.
補足)分割代入による簡略化
PaintSizeはシンプルな構造をしているため, 分割代入を利用するとコードを簡略化することが可能です.
プロパティ別の描画サイズ
paint関数を参照しているプロパティによってPaintSizeから得られるノードの描画範囲の値は変化します.
PaintSizeで得られるサイズ
paint関数を参照するプロパティ | PaintSizeオブジェクトから得られる値 |
background-image | background-sizeに対応するサイズ |
mask-image | mask-sizeに対応するサイズ |
border-image-source | borderを含んだサイズ
border box, element.getBoundingClientRect()で得られる値 |
list-style-image | font-sizeに比例したサイズ(0.5em)† |
†:marker疑似要素で指定できるとありますが, 実装しているWEBブラウザが存在しません. |
以下はbackground-sizeプロパティを指定した例です. 引数sizeにbackground-sizeが渡されていることが判ります.
以下はlist-style-imageプロパティでの例です.
パターン敷き詰めについて
現状ペイント処理内で動的にパターン画像を生成する方法がありません(createPatternメソッドに渡す画像のソースが存在しない). そのため, 画像パターンを敷き詰める場合は, スクリプトの繰り返し構文を使って敷き詰めるか, background-sizeをパターン画像の大きさとしbackground-repeatプロパティで敷き詰めるしかありません.
border-image-sourceによるカスタムボーダーの定義
border-image-sourceプロパティを使ってborderグラフィックをカスタマイズする場合は, border-width値とborder-image-slice値とを一致させるようにするとうまく辻褄が合います.
複数箇所に分かれたノードに対する動作
span要素等の(ブロックレイアウトを採らない)フレージングコンテンツは, 改行等により複数箇所に分割された形でレイアウトされることがあります. この場合PaintSizeには分割される前の(仮想的な)ノードのサイズが渡され, paintメソッドで描いた内容がスクリーンに分割表示されます.
スクリーン画素を基準とした図形描画
コンテキストオブジェクトはズーム倍率やスクリーン画素密度を加味した値で予めスケーリングされています. そのため, スクリーンの画素位置を基準とした図形描画を要する場合は, devicePixelRatioから得られたスケーリング倍率を元にスクリーン画素数を基準とした物理サイズを求めるようにします.
- PaintWorkletGlobalScope.devicePixelRatio
- (ズームを含んだ)スクリーンの画素密度を取得します
paintメソッドの引数とdevicePixelRatioとの関係は次のとおりです.
- paintメソッドの第1引数に与えられるコンテキストオブジェクトは物理スケールと比べてスケールがdevicePixelRatio倍されています.
- paintメソッドの第2引数に与えられるサイズは物理スケールと比べて大きさがdevicePixelRatio分の1されています.
これらの関係を利用した例を示します. このようにするとブラウザのズーム倍率を変化させたとしても, 図形の境界が物理的なピクセル位置に来るためグラフィックの品質劣化が発生しません.