CSS Painting APIの基本的な使い方・まとめ

written by DEFGHI1977.

[重要]HTTPS環境での動作はこちら(APIが正常動作)/HTTP環境での動作はこちら(APIが非動作)

本文書はSVG要素の基本的な使い方・まとめの姉妹版canvas要素の基本的な使い方・まとめのさらなる姉妹版として作成を開始しました. CSS Painting APIは概ねグラフィックの生成処理とスタイルシートを介したパラメータの与え方の二つから構成されており, 前者は一部を除きcanvas APIと互換があります. そのため, グラフィック描画に伴う基本的な考え方についてはcanvas要素における文献を参照してもらうこととして, ここでは主に後者における(実際に動いている)ライブコードを交えた検証・考察を行います. なお, 筆者はCSSの基本的なアーキテクチャについてはさほど詳しくないため, 内容の端々に誤解や間違いが含まれていることがあります.

現状CSS Paint APIをサポートする環境はChrome65以降と限られているほか, 検討中のAPI構成が将来的に改訂される可能性もあるため, 動かなくなるリスクがある点につき予めご了承下さい. また実装が未熟なAPIも含まれていて, 動作がstable/beta/unstable版の全てで異なる等カオスの極みです.

CSS Typed OM仕様の再検討の煽りを受け, CSS Painting APIにおけるラスタ画像データの利用が現在不可能となっています.

目次

更新履歴

CSS Painting API概観

CSS Painting APIとは

CSS Painting API(CSS Paint API)とはWEBブラウザがCSSを介して行っている描画処理の一部をJavaScriptで記述するための仕組みです. このAPIで生成したグラフィックは通常のラスタ画像と同様にDOMノードの背景等に適用することが出来ます.

背景

HTML5の利用が広まる中, WEBブラウザが提供する機能は年々向上しています. これはWEBに求められているものがシンプルな情報の公開から, より複雑なアプリケーションプラットフォーム(≒OS)としての役割に移行している証左であり, WEBサイトの作り方も愚直にHTMLやCSSを記述する方法から, アプリケーション構築の方法論に則ってシステマチックに行われることが多くなっています.

この流れの中で, 現在WEBが直面している問題の一つとして基本機能だけでは対処が難しい要件の増加が挙げられます. WEBが発明された当初からユーザーは既存のWEB仕様の範囲内で表現することを余儀なくされており, この前提のもとで仕様は少しずつ改善・拡張を繰り返してきました. しかしプラットフォーム化したWEBにおいては日増しに高まっていくユーザーの要望に対し, その都度標準仕様を検討しブラウザ側の実装を待っていては時間がかかりすぎます. そこでまずWEBを柔軟にカスタマイズできる環境を用意しておき, ユーザー側に(個々の責任において)その実装を任せるという方針の転換が求められています.

そのため2010年台前半よりWEBを構成する各要素(HTML, DOM, JavaScript, CSS)の動作を外部から操作/最適化/部品化する仕組みの導入が試みられており, 一部はWeb Components仕様やECMAScript modules, asm.js, Web Assembly仕様として結実しつつあります. またCSS分野においてもCSS Painting API仕様としてスタイル付けそのものに手を加える手段についての検討が進んでいます.

WEB分野毎の機能拡張

関連する仕様群

CSS Painting API仕様は単体で策定されたものではなく, CSS-TAG Houdini Task Forceにより続けられているCSSによる「スタイル付け機構」そのものをスクリプトの制御下に入れる試みの一つです. この部会では以下の仕様が並行して検討されています.

参考: CSS-TAG Houdini Editor Drafts

このように全体で見るとCSSの根幹に関わる壮大な構想であることが判ります. メモ程度の仕様と呼ぶレベルに到底達していないものも含まれているとは言え, CSS Painting API及びそれに関わる仕様(★を付けたもの)は検討すべき対象が絞られていて導入に伴う効果が判りやすいことから先行して実装が始まっています.

特徴

CSS Painting APIを導入することにより, 次のような効果が得られます.

その一方で, 現状外部リソースの参照に大きな制約が課せられているため, あまり凝ったことは出来ません. 処理の一部をスタイルシート側に任せることで全体として見通しの良い設計が可能となるかもしれない程度のものであり, 過度に期待すると肩透かしを食うかもしれません.

API構成

CSS Painting APIの構成を図にまとめると次のようになります.

CSS Painting APIの構成

実装例

CSS painting APIが実際に動作するためには, 次の条件を充たす必要があります.

こうすることで, WEBブラウザは登録済みのペイント処理を必要に応じて呼び出します. 以下にCSS painting APIの利用例を示します.

動作例:background-image: paint(sample);が適用されたノード

このように, 自作のペイント処理が標準のCSS関数(例えばlinear-gradient)と同等に呼び出すことが可能となるのです.

動作環境

大前提としてJavaScript/スタイルシートが有効化されていることがありますが, 実際のCSS Painting APIの動作は現状Chrome65以降でのみ確認できます. なお作成したスクリプトの動作は現状ローカル環境では確認できません. 別途HTTPSをサポートしたWEBサーバー(もしくはlocalhostで稼働する通常のWEBサーバー†)を用意して下さい. 本APIはWEB環境を強力に書き換えることが可能であることから, 実行可能な環境に制限が掛っています.

またCSS Painting APIのポテンシャルを引き出すには, CSS Properties and Values API及びCSS Typed OMとの連携が不可欠です. これらを試すには「chrome:flags」からExperimental Web Platform featuresを有効化して下さい.

サポート判定

WEBブラウザがCSS Painting APIをサポートしているかを判定するには幾つか方法があります. CSS Painting APIが広く一般化するまでは代替となるスタイルを設定しておきましょう.

スクリプトによる判定

スクリプトからは変数CSS.paintWorkletの有無で判断できます.

CSSによる判定

CSSによる判定はペイント処理が登録されている前提で@support規則を用いることで行えます. また, CSSプロパティに無効な値を指定した際にその内容が無視される特性を利用する方法もあります.

スタイル設定結果:span1 span2

競合する仕組み

CSSで用いる背景画像等を動的に生成する方法としてはこれまでも幾つか考案されており, 専用のAPIを持つ環境もあります. 何れもcanvas要素(もしくはそれと同等の機能)を利用している点でCSS Painting APIとアイディアをともにしていると言って良いでしょう.

しかしこれらはあくまでもCSSでのグラフィックの共有です. 一方のCSS Painting APIではグラフィックの描画手順が共有されるため, 同一スタイルが設定されていてもノード毎にペイント処理が呼び出されます.

メディアクエリとCSS Painting API

メディアクエリを用いてWEBページの動作環境毎に見た目を切り替える場合, 次の二つのストラテジが考えられます.

CSS Painting APIでは作成したペイント処理に固有のIDを割り当てる際に, ID値が重複することを許していません. そのため, スタイル適用条件の判定結果がユーザー操作(例えばウィンドウリサイズ)で変化しうる場合は前者が適しています. 一方, ハードウェア条件(例えばスクリーンの画素密度)等の容易に変化し得ない条件でのスタイル切り替えであれば, どちらの方針でも問題は出ません.

ペイントワークレットモジュールの基本

ペイントワークレットとは

ペイントワークレット(Paint Worklet)はCSS painting APIを構成するために新たに導入された特別なJavaScirptコードの実行環境です. ここで定義したコードはCSSのペイントタスクに組み込まれます.

ペイントワークレット内では次の処理を行います.

ペイント処理はWEBブラウザから頻繁に呼び出されうるため, 何れも動作が軽快となるように処理の内容を吟味する必要があります.

ペイントワークレットモジュールの読み込み

CSS painting APIを使うには, ペイントワークレットに対してペイントワークレットモジュールを読み込ませる必要があります.

window.CSS.paintWorklet.addModule(moduleURL, options)
指定したURLのJavaScriptモジュールをペイントワークレットに読み込ませる. モジュールの入手にCORSリクエストを要する場合は引数optionsに認証パラメータ{credentials: "omit" | "same-origin" | "include"}を設定する.

クロスオリジンでの読み込み

JavaScriptモジュールをクロスオリジンで読み込む際にCORS(Cross-Origin Resource Sharing)リクエストが必要な場合は, 引数optionsに認証パラメータを指定します. この内容はFetch APIで定義されています.

スクリプト読み込み後の操作

モジュールの取得・分析・実行はscript要素によるものと同じく非同期に行われるため, addModuleメソッドはPromiseオブジェクトを返します. モジュール登録後の後処理を要する場合(並びにモジュールの登録順に意味がある場合)はこのPromiseに対して処理を登録します.

複数のモジュールを読み込む場合はPromise.allメソッドを用いてPromiseを一つにまとめると良いでしょう.

blob URIスキームを用いたワークレットモジュールの動的定義

モジュールの指定には外部JavaScriptファイルの他, JavaScriptソースをBlob化したものを使うことも可能です. 例えばHTMLにモジュールを埋め込んでおき, その内容をaddModuleメソッドに渡すには次のようにします.

この方法を用いると, モジュールの一部を書き換えてペイント処理の一部(例えばid)をパラメータ化するといった仕組みを実現できます.

ペイントワークレットグローバルスコープでのスクリプト実行

addModuleメソッドで読み込まれたモジュールはペイントワークレットグローバルスコープ(PaintWorkletGlobalScope)と呼ばれるwindow環境とは分離された固有の環境で実行されます. このスコープは次の特徴を持ちます.

利用可能なAPI

以下にペイントワークレットグローバルスコープ内で利用可能なAPIについてまとめました.

利用可能なAPIの一覧
JavaScript組み込みのAPIArray, ArrayBuffer, DataView, Date, Error, Function, JSON, Map, Math, Number, Object, Promise, Proxy, RegExp, Set, String, Symbol, WeakMap, WeakSet, 型付き配列
decodeURI, decodeURIComponent, encodeURI, encodeURIComponent, escape, eval, isFinite, isNaN, parseFloat, parseInt, undefined, unescape
CSS painting API固有のAPIregisterPaint, devicePixelRatio, PaintRenderingContext2D (CanvasRenderingContext2Dの機能制限版), PaintWorkletGlobalScope(ペイントワークレットモジュールの実行環境), PaintSize(描画範囲を表すパラメータオブジェクト)
描画処理に関わるAPIPath2D(fill/stroke操作の引数に用いる)
CSSに関わるAPIStylePropertyMap, StylePropertyMapReadOnly, CSSStyleValue及びその派生(CSSXXXValue)オブジェクト群
その他のAPIconsole
これ以外にもグローバルスコープで定義されていないものの, paint関数の引数やその他のメソッドの戻り値として得られるオブジェクト型があります.
一方setTimeout, setInterval, alertといった機能はDOM(Windowオブジェクト)で定義されたものであるため利用できません. その結果非同期処理が発生しないことから, Promiseオブジェクトの使い途がありません.

このように利用可能なAPIに大きな制約があるのは, ペイント処理はレイアウトの変化をトリガとして同時並行的に呼び出されうるからです. 与えられたパラメータを元に高速な描画を行うことが何より優先されるため, ファイルI/O等のロックが発生しうる処理やそもそも負荷の高い処理が入る余地が無いのです.

スコープオブジェクトの取得

スクリプトを実行している環境に対応するPaintWorkletGlobalScopeオブジェクトは, 次のようにして取得します.

ライブラリの定義と利用

ワークレットモジュール間で処理を共有する(ライブラリを定義する)方法には次の二つがあります.

ECMA Scriptモジュールの読み込み

ワークレット環境ではimport文による外部ES(ECMA Script)モジュールのインポートが可能です. そのため, ペイントワークレットモジュール毎に共通のESモジュールをインポートすることで処理を共通化することが可能です. 読み込んだモジュールはペイントワークレットグローバルスコープ内で動作するため, 利用できるAPIに違いはありません.

特に問題がない限りこちらの方法を利用しましょう.

プロトタイプオブジェクトへの機能定義

ペイントワークレットモジュールが実行されるスコープはスクリプト毎に分離されますが, 変数PaintWorkletGlobalScope.prototypeやPaintRenderingContext2D.prototype等のプロトタイプオブジェクトは全てのスコープで共有されています. そのため, ここにペイント処理横断的に利用したい処理(ユーティリティ・ライブラリ)を登録しておくことが可能です.

とは言え, この手法は一歩間違えるとプロトタイプ汚染(コードの追跡が困難となる)やライブラリ依存(コードの再利用が難しくなる)という根深い問題を引き起こすため, 出来ることなら使わない, もしくは用途を限定(例えばデバッグ目的でのみ利用する等)すべきでしょう.

描画処理の動作

ペイントクラスの定義と登録

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
利用可CanvasStatesave, 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
CanvasPathDrawingStyleslineWidth, 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-imagebackground-sizeに対応するサイズ
mask-imagemask-sizeに対応するサイズ
border-image-sourceborderを含んだサイズ
border box, element.getBoundingClientRect()で得られる値
list-style-imagefont-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との関係は次のとおりです.

これらの関係を利用した例を示します. このようにするとブラウザのズーム倍率を変化させたとしても, 図形の境界が物理的なピクセル位置に来るためグラフィックの品質劣化が発生しません.

CSSデータ型とパラメータ設定

ペイント処理におけるパラメータ渡し

ノードに設定されているスタイルの内容からペイント内容を変更する場合, 次の二つの何れかを利用します.

得られる値の内容

上記の何れの方法においても得られる値はノードにおける現在のプロパティ値, つまりノードの状態(hover, focus, active等), アニメーション等の効果, calc関数による演算等を踏まえた値が得られます.

CSSプロパティの参照

ペイントクラスに予めinputPropertiesプロパティを設定しておき, その中にペイント処理内部で使うCSSプロパティの名称(ショートハンドプロパティを除く)を配列として宣言しておきます. するとpaintメソッドが呼び出された際に, 第3引数に渡されるStylePropertyMapReadOnlyオブジェクトを介してノードのスタイル値(現在値)を取得できます.

宣言するCSSプロパティには既存のものの他, "--"から始まるカスタムプロパティを用いることができます.

StylePropertyMapReadOnly.get(propertyName)
指定した名称に対応するノードの現在のCSSプロパティ値をCSSStyleValueオブジェクトとして取得します.
StylePropertyMapReadOnly.getAll(propertyName)
指定した名称に対応するノードの現在のCSSプロパティ値のリスト(例: background-image)をCSSStyleValueオブジェクトの配列として取得します.
CSSStyleValue.toString()
プロパティ値の文字列表現を取得します. 得られる値はCSSコメント(/**/の範囲)を除いた[:から;]の範囲です.

例を示します.

既存のプロパティを使った場合はCSSStyleValueにvalueプロパティやunitプロパティ(単位)等が設定されており, 値の詳細情報が取得できます. なお, その内容はCSSプロパティによって異なります.

SVG向けのCSSプロパティの活用

CSSで定義されているプロパティの中にはSVG由来のものがあり, それらはペイント処理を行う上でも有用です.

ペイント処理へ流用可能なCSSプロパティ
CSSプロパティ対応するcanvasAPI内容・備考
fillfillStyle塗りの色
fill-rule(fillメソッド等)塗りつぶし領域の算出
strokestrokeStyleストロークの色
stroke-widthlineWidthストロークの幅
stroke-linecaplineCapストローク端点の形状
stroke-linejoinlineJoinストローク頂点の形状
stroke-miterlimitmiterLimitストローク頂点の尖りの限界
stroke-dasharraysetLineDash破線のスタイル
「px」を取り除き, 「,」で分割して利用する.
x,y-x座標, y座標
cx,cyarc, ellipse円/楕円の中心
r,rx,ryarc, ellipse円/楕円の半径, x軸/y軸方向の半径
dfill/stroke等図形形状

取得される値の範囲

CSSStyleValue.toStringで得られるデータはスタイルシートに記述された:から;の範囲(コメント・/*〜*/の範囲を除く)です. スタイルシートの可読性のために挿入されたインデントや空白文字列が含まれるため, 必要に応じてその内容を除去します.

contentプロパティによるノード情報の取得

contentプロパティではattr関数を用いてノードの属性値を取得できます. この内容をペイント処理内に持ち込むことで属性値を基準としたペイント処理が可能となります.

カスタムプロパティとCSSデータ型

カスタムプロパティはそのままでは採りうる値の型が定まっていませんが, CSS.registerPropertyメソッドを使って予め型を明示しておくと次のようなメリットが得られます.

CSS.registerProperty(descriptor)
カスタムプロパティに詳細な型情報を設定します. descriptorは次の内容を含むオブジェクト.
name
カスタムプロパティの名称(--xxx)を指定します.
syntax
プロパティがとりうる値の型・文法を専用の記法(Value Definition Syntax)で指定します.
inherits
プロパティの値が未設定だった場合, 値を親要素から継承するかどうかを指定します. true:する, false:しない, 初期値はfalse
initialValue
プロパティの初期値を指定します.
CSS.unregisterProperty(name)
カスタムプロパティの定義を削除します.

例を示します. 型付けすることで引数propertiesからCSSUnitValueオブジェクト(数値と単位)が取得出来ます.

CSSデータ型とCSSStyleValueオブジェクト型

以下にsyntaxパラメータに記述する記法と, それに対応するCSS基本データ型及びpaintメソッドに渡されるCSSStyleValueオブジェクトの型について示します. なおこの記法は後述するpaint関数のパラメータの定義にも用います.

CSSデータ型の一覧(一部)
記法CSSデータ型対応するCSSStyleValue型実装しているプロパティ等
未指定CSSUnparsedValuetoStringメソッド(値の文字列表現)
*任意
<length>長さ(非負値)CSSUnitValuevalue(値), unit(単位)
<number>数値
<percentage>パーセント値
<integer>整数
<angle>角度
<time>時間
<image>画像(url)CSSURLImageValueurl, 画像のサイズ, 画像の読込状況等
<transform-list>座標軸変換のリストCSSTransformValue-
<color>CSSKeywordValuevalue(値)
[文字列]文字列値CSSKeywordValue
※型判定が失敗・未実装CSSStyleValuetoStringメソッド(値の文字列表現)
†color型に対する専用の型は現在未定義です(今後見直される可能性があります).
‡型は定義されているもののうまくオブジェクトが取れないかもしれません.

この他に, 「*(任意の値)」, 「|(何れかの値)」, 「+(任意個数)」の記述子を用いることが可能です.

単位の扱い

取得したデータの型(CSSUnitValueオブジェクトとして得られるもの)によってはJavaScriptで扱えるように値の変換が必要となります. 例えばangle型であれば, 与えられた単位(deg, rad, grad, turn)毎に値をラジアン基準のものに換算します. またlength型のように自動的に値が単位pxに揃えられるものもあります.

CSSUnitValue.value
プロパティの値
CSSUnitValue.unit
プロパティの単位 px, cm, %, deg, rad等, 設定される内容はCSSデータ型ごとに異なります

カスタムプロパティの共有

複数のペイント処理で利用するカスタムプロパティを共有する(=ペイントワークレットモジュールにおける(疑似)環境変数を定義したい)場合は, 次のようにします.

  1. 共有したいカスタムプロパティを全称セレクタ(*)に設定するか, カスタムプロパティを継承可能(inherits: true)とし:rootノードに設定する.
  2. 親となるペイントクラスを定義し, その中で共有したいカスタムプロパティの利用を宣言する. 個別のペイントクラスはこの親クラスを継承する.

ここでは継承可能なカスタムプロパティを使った例を示します.

paint関数の引数の参照

ペイントクラスに予めinputArgumentsプロパティを設定しておき, その中でCSSデータ型の配列を設定しておくと, スタイルシートのpaint関数記述側で記述したパラメータがペイントクラスのpaint関数の第4引数に配列として渡されます.

例を示します.

各引数に具体的なCSSデータ型を指定しておくと詳細な内容が取得できます.

引数のパターンが合わないケースの動作

CSSから渡した引数の構造がinputArgumentsの内容に合致しない場合はそのペイント処理は無視されます.

補足)分割代入による簡略化

ペイント処理の引数の内容が固定的な場合は, 分割代入を用いることでコードを簡略化出来ます.

変数に値の列を指定する

型を任意型とすることで, paint関数の引数に任意個数の値を渡すことが可能となります.

型指定に+キーワードを追加することでpaint関数の引数に任意個数の値を渡す方法もあります.

ペイント処理の実行タイミングとアニメーション

paintメソッドの呼び出しタイミング

ペイントクラスのpaintメソッドは, プロパティ値の変化やウィンドウのリサイズやズーム・アニメーション等によりペイント処理を参照しているノードの再描画が必要と判定され, かつそのノードがスクリーンに表示されている場合に限り実行されます.

次の例ではユーザーのホバー操作に起因するリサイズが発生するため, それに伴いpaintメソッドが呼び出されます.

ペイント処理の強制

スクリーンに表示されているノードのペイント処理をスクリプトから強制するには, 専用のカスタムプロパティを用意しその値を書き換えるようにします.

アニメーションの実現

ノードの大きさが変化するアニメーションであればペイント処理が自動的に呼び出されますが, それ以外の場合は処理内部で利用するプロパティを次の何れかの手段で継続的に変化させることでペイント処理を誘発します.

例を示します.

型付けしたカスタムプロパティのアニメーション化

カスタムプロパティに型を設定しておくことでCSSアニメーションの対象となるため, その内容を元にペイント処理を行うことでアニメーションを実現できます.

paint関数のアニメーション化

background-image等の画像を参照するプロパティはCSSによるアニメーション化の対象外であることから, paint関数そのものをアニメーション化する場合はスクリプト側で値を変化させます.

CSSアニメーションの応用

CSSアニメーションの仕組みを活用することで様々なアニメーションが作り出せます.

持続するアニメーションの実現

特に終了タイミングが決まっていないアニメーションは, paintメソッドのパラメータとして経過時間を渡すようにし, スタイルシート側でこのパラメータを長期間に亘って書き換えるようにします.

時刻アニメーションの実現

時計等の現在時刻を元にアニメーションを描くにはダミーのプロパティを継続的に書き換えるようにし, その書き換えをトリガとしてペイント処理を強制します.

ペイント処理への画像入力

ペイントワークレットグローバルスコープにおける画像の取扱い

効率化の観点からペイントワークレットグローバルスコープから外部画像を能動的に取得する手段はありません. 同様に画像のデコード機構そのものが欠けているため, スクリプトコードにラスタ画像データを埋め込んでおくことも出来ません. 画像データを入手する唯一の手段は下記に示すCSSプロパティを介したものです.

パラメータを介した画像データの受け渡し

スタイルシート内でurl関数経由で参照している画像はペイントワークレットモジュール内でCSSURLImageValueオブジェクトとして取得出来ます. このオブジェクトはCanvasImageSourceインターフェースを実装するため, drawImageメソッドの引数として利用可能です.

CSSURLImageValue.intrinsicWidth
画像の幅
CSSURLImageValue.intrinsicHeight
画像の高さ
CSSURLImageValue.intrinsicRatio
画像のアスペクト比
CSSURLImageValue.url
画像のURL
CSSURLImageValue.state
画像の読込状況(unloaded/loading/loaded/error)

既存のCSSプロパティの参照

border-image-source, list-style-imageプロパティが参照する画像はペイントクラスの入力値として利用出来ます.

background-imageプロパティの自己参照

カスタムプロパティ・引数を介した画像の参照

カスタムプロパティ及びprint関数の引数を定義する場合はCSSデータ型をimageとすることでCSSURLImageValueオブジェクトが得られます.

ペイント処理間での画像共有

前に示した環境変数定義のテクニックをimage型に応用することで, 画像を複数のペイント処理で共有出来ます.

ここでは全称セレクタを用いた例を示します.

canvas要素とペイント処理との連携

canvas要素に描いた内容をtoDataURL/toBlobメソッドを用いてURL文字列化することで, CSSプロパティを介してペイント処理にその内容を渡すことが出来ます. これによりペイントワークレット内部で行うことが出来ない, もしくは(負荷の観点から)行うべきではない処理を外部のcanvas要素側で事前に行わせておくことが可能となります.

ベクタ図形のワークレットコードへの埋め込み

SVGパスで記述されたベクタ図形はPath2Dオブジェクトを介してコンテキストの描画処理に渡せます. 簡単な例を示します.

この仕組みを利用すると単純なラスタ画像であればベクタ化した上でスクリプトに埋め込むことが可能です.

ペイント処理による動画の再生

広義の意味合いにおいて, WEBで扱うアニメーションにはGIFやAPNG等のアニメーション画像や本来video要素で再生すべき動画等も含まれます. しかしこれらの動画をCSS背景で直接再生することは出来ません. 一度アニメーションを静止画の集まりに分解し, それをもとに背景画像を逐次書き換える(いわゆるパラパラアニメーションの)手法で動画を再現するようにします.

パラパラアニメーションの実現手法

ペイント処理を用いてパラパラアニメーションを構成する場合, API構成上開始時刻や描画位置等の状態を保持することが出来ないため, CSSアニメーションが算出した描画位置を元にグラフィックの描画を行います. 例を示します.

パラパラアニメーションのソース画像

これは従来のbackground-positionをずらすことによる背景画像のアニメーション化手法に似ていますが, 特筆すべき点としてborder-image-source等の画像をアニメーション化可能な処にあります.

ペイントクラスの動作確認

ペイントワークレットモジュールの動作確認

ペイントワークレットモジュールは一般的なスクリプトと比較し, 動作検証に利用できるAPIが限られているうえ, コード呼び出しのタイミングを特定することが困難です. そのため複雑なグラフィック生成を要する場合は, APIに互換性があるcanvas要素で大枠を作ってからその内容をペイント処理に移植すると良いでしょう.

ペイントクラスのドライバの作成

ペイントクラスの動作をwindow環境で検証するにはcanvas要素を用いたドライバを作っておくと便利です.

例を示します.

これを通常のペイントワークレット環境で実行すると次のような結果が得られます.

これらをcanvas要素に対して動作させてみましょう. 必要となる処理は次のとおりです.

Element.computedStyleMap()
ノードの現在のスタイル値を取得します.

上記方針の元でドライバを作成し, 実行した結果を示します. ペイントワークレット環境下での出力と同じであることが判ります.

ペイントクラスドライバによるペイントクラスの実行結果

参考文献