canvas要素の基本的な使い方まとめ

Written by defghi1977@xboxLIVE

本文書はsvg要素の基本的な使い方まとめの姉妹版として作成を開始しました. canvas要素の仕様は現在進行中で色々と変化しているため一筋縄でいかないのですが, 大方のAPIについて書き上がったので公開してみることとしました. なお, まだ使えない機能等満載(そのためスクリプトエラーが発生する場合もあります)だったり, 内容に間違いがあっても中々検証することができないので, その部分を了承した上でご利用下さい…

更新履歴

1・canvas要素の概観

canvas要素とは

canvas要素はHTML5で採用されたWEBブラウザ上で動的にグラフィックを描画するための仕組みである. もともとApple社が自社製OSの機能向上を目的に, 同社製WEBブラウザSafariの独自拡張として策定したものであったが, その利便性が認識されるとMozilla(FireFox)やOpera等のブラウザベンダーも追随するようになり, HTMLにおける事実上の標準仕様として認識されるようになった. この流れから, HTML5では正式仕様として取り入れられ, 現在Internet Explorerを含むほとんどのブラウザ上でcanvas要素が利用可能となっている.

canvas要素にグラフィックを描くには, DOMで定義されたグラフィック描画APIをJavaScriptから操作していくこととなる. APIには単純なグラフィックを描くだけでなく, 画像の合成を行ったり画像データへのアクセスを可能とするものなどが過不足無く備わっていて, 画像に関わる広範な処理を行わせることができる. またcanvas要素に描かれた描かれたグラフィックはimg要素で表示している静的な画像と同等に扱われ, ラスタ画像ファイルとして取り出すこともできる.

正確を期すと, canvas要素はグラフィックの出力を司るノードであり, それ自身は描画機能を持たない. 描画を行うのは専らCanvasRenderingContextオブジェクトである. こうすることで描画したい内容(2D/3D)に応じて適切なAPIセットを選択可能としている.

canvas要素の今後

現状ではHTMLにおけるグラフィック描画要素としての側面が強いが, 今後はより広範な目的を充たすためのWEB環境における標準的なグラフィック加工インターフェースとしての役割を担う. 例えばWEBカメラで撮った映像をcanvas要素で加工し, それを動画として出力すると言ったことすら可能となる. またSVG2においてもcanvas要素のサポートが予定されており, canvas要素の活躍の場が更に広がるものと期待される.

ベクタ画像とcanvas要素

canvas要素にはベクタデータを描くためのAPIが備わっているため, canvas要素がベクタ画像を扱うと説明されることもある. 実際SVG等のベクタ画像をcanvas要素上に描く事もできる.

SVGとcanvas要素の使い分け

しばしばHTML5での競合する技術としてSVGが挙げられるが, canvas要素とは明確な使い分けが可能である. 例を交えて説明する.

以下, 「グラフィック上での動作に応じて内容を書き換える」ことをSVGとcanvas要素とで実現したものだ.

SVGを使った例

was clicked.
//SVGのコード <svg id="svgsample" width="200px" height="200px" style="border:2px outset black;float:left;"> <style> #svgsample>*{fill:blue;} #svgsample>*:hover{fill:red;} </style> <circle cx="50" cy="50" r="50" class="clickable"/> <rect x="110" y="10" width="80" height="80" class="clickable"/> <text y="200"><tspan></tspan> was clicked.</text> </svg>

SVGではグラフィックの構造をDOMツリーとして記述することが可能で, 既存のイベントモデルやCSS等の仕組みを活用可能だ. このことから一般に構造がしっかりとしているユーザーインターフェースやクリッカブルな地図やグラフ等の用途に向く.

canvas要素を使った例

canvas要素にはグラフィックに構造を与えるものが少なく, 全ての処理を自分で記述しなければならない. その結果先ほどのSVGの例と比べてコードの記述量が増えてしまっている.

その一方でcanvas要素には画像を操作するための低レベルなAPIが提供されており, 機能的な制約がほとんど存在しない. また, SVGと異なりグラフィック構造を自由に定義することが出来るため柔軟性に富む. そのためcanvas要素は, 先ほどの例以外の用途(ゲームや画像の生成・加工等)において有用である.

このように実はSVGとcanvas要素とは相補的な立ち位置にあるため, 組み合わせて利用することも多い. 実際SVG2ではHTML5よりcanvas要素が導入される事になっており, 今後両者の連携がより密接なものとなっていく.

canvas要素の利用が可能な環境

canvas要素は現在ほとんどのブラウザ(FireFox, Chrome, Opera, Safari, Edge, Internet Explorer9以降)で利用可能である. とは言えcanvas要素の仕様がある程度確定したのはつい最近(HTML5勧告)のことであり, ブラウザの種類やリリース時期によって利用可能なAPIの種類及び名称が大きく異なる. そのため仕様にあるからと言ってcanvas要素の全ての機能が利用可能というわけでない.

また, 仕様で定められていたとしてもブラウザごとに異なるレンダリングエンジンの特性により細かい描画の差が発生する. そのため, 完璧な互換動作が求められる場面では多大な検証コストが発生しうる.

canvas要素のポリフィルライブラリ

また, レガシーIE(6〜8)についても, 擬似的にcanvas要素に対応させるJavaScriptライブラリが提供されている.

とは言えcanvas要素以外のAPIの機能向上に対応できていないため, 実現可能な機能は大きく限定されてしまう. 特に断りがない限りこれらのブラウザはサポート対象外としたほうが良い.

canvas要素に対応したスクリプトライブラリ

canvas要素に特化したJavaScriptライブラリは主に次の3種類に分けられる.

ローカルでの動作検証

ブラウザによっては, ローカル環境上ではセキュリティの問題からcanvas要素の全ての機能を利用することができない. 動作検証に支障をきたすので, 適宜ブラウザ設定を変更しておくと良い.

また, APIによっては専用のフラグを有効化しないと動作しないものがある. これらは仕様・機能の検証中の機能なので, 動作検証の目的以外では利用してはならない.

2・canvas要素によるラスタ画像の描画

canvas要素を取り巻くオブジェクト群

canvas要素はJavaScriptに対する画像描画APIと考えることが出来る. 以下はcanvas要素を取り巻くオブジェクトの相関を表しており, 黄色いものはcanvas要素仕様に直接関わるものだ. グラフィックの入出力まで含めると, 非常に多くのオブジェクトと関わりを持つことが判る.

canvas要素の基本構造部分

CSSに関わる部分

グラフィック描画を扱う部分

画像入出力に関わる部分

バイナリデータ操作に関わる部分

バックグラウンド動作に関わる部分

ストリーミング出力(WebRTC)に関わる部分

補足)利用可能なAPIについて

なお, 環境によってはここに記載されているAPIの全てが利用可能というわけではない. とりわけInternet Explorerでは(ライフサイクルの観点から)他のブラウザと比べてバージョンごとに利用可能なオブジェクトやAPIに大きな制約が発生している. 以下のテーブルはこのブラウザで利用可能なオブジェクトを示しており, ピンク色の機能は利用できないことを表している.

オブジェクトのサポート状況
CanvasPixelArrayUint8ClampedArrayatobbtoaPath2DWorkerBlobURLrequestAnimationFrameImageBitmapImageBitmapRenderingContext
メソッドのサポート状況(HTMLCanvasElement)
toBlobmsToBlob
メソッドのサポート状況(CanvasRenderingContext2D)
drawFocusIfNeededaddHitRegionscrollPathIntoView

グラフィック描画の流れ

canvas要素によるグラフィックの描画は単体では動作せず, JavaScriptから先程示したオブジェクトを操作することで実行される. 以下にその処理の流れについて示す.

  1. canvas要素を記述する(事前準備)
    HTMLソースコードのグラフィックを描きたい箇所にcanvas要素を記述する. 通常img要素と同じようにレイアウトして構わない. 直接スクリーンにしないのであれば必要ない.
  2. canvas要素を入手する
    document.createElement("canvas")を用いてカンバス要素を生成するか, 既存のカンバス要素をdocument.getElementById / getElementsByTagName / querySelector / querySelectorAllメソッド等で取得する. 得られるオブジェクトはHTMLCanvasElementオブジェクトとなる. なお, createElementメソッドによってcanvas要素を生成した場合はその要素をDOMツリーに挿入せず, そのままメモリ上でグラフィックを描く事もできる.
  3. canvas要素のサイズを設定する
    widthプロパティとheightプロパティを使って画像サイズを設定する. canvas要素そのものに属性値が設定されている場合はこの処理は不要となる.
  4. コンテキストオブジェクトを取得する
    getContextメソッドを使ってコンテキストオブジェクトを取得する. コンテキストは一般に「文脈」と訳されるが, canvas要素においては絵を描くための「道具」を表す概念である.
  5. コンテキストオブジェクトを介してグラフィックを操作する

canvas要素を記述する

HTML文書中で動的にグラフィックを描きたい箇所にcanvas要素を記述する. canvas要素に指定可能な属性を示す.

width
canvas内部のグラフィック幅を指定する.
height
canvas内部のグラフィック高を指定する.
id,class,style,title…
HTMLの要素共通の属性.

canvas要素はレイアウト上img要素と同様に振る舞う(フローコンテンツ)ため, CSSを用いて自由に配置することが可能だ. その際, 後でスクリプトが目的のcanvas要素を検索できるようにid属性やclass属性を定義しておくと良い. また, width属性やheight属性を記述しておけばスクリプトでのサイズ指定を省くことが出来る. 例を示す.

<canvas width="200" height="200" id="cmvs">
	<div>canvas要素が表示できなかった際の代替コンテンツ</div>
</canvas>
<!--
canvas要素はフォールバックコンテンツを必要とするので,img要素のように単一タグで記述することは出来ない. 
(XHTMLの場合では問題ない)
-->
<canvas width="200" height="200" id="cmvs">
<canvas width="200" height="200" id="cmvs"/>

canvas要素はグラフィックを表すので, JavaScriptが無効化されている場合やテキストブラウザやスクリーンリーダーなどのためのフォールバック(代替)コンテンツ指定しておこう.

スクリプトの記述

スクリプトコードを記述する場所に特に制限はないが, DOMを経由してcanvas要素を取得・追加することが多いため, DOMの解析が終了したあと, つまりdocumentのDOMContentLoadedもしくはwindowのloadイベントに処理を登録するのが一般的だ. 簡単な例を示す.

このように全ての画像処理を手続き的に記述することとなるので, グラフィックが複雑になればなるほどコードが長くなる. 従って処理の内容に応じて共通機能をサブファンクション化したり, 外部ライブラリ化するなど処理の流れを明瞭化しておくと, コードの管理がやりやすくなる.

HTMLCanvasElement.width
canvasのグラフィック幅. width属性値に対応する. グラフィック描画後に値を変更すると, グラフィックやスタイル設定が初期化される. width属性未設定の場合300(px).
HTMLCanvasElement.height
canvasのグラフィック高さ. height属性値に対応する. グラフィック描画後に値を変更すると, グラフィックやスタイル設定が初期化される. height属性未設定の場合150(px).
HTMLCanvasElement.getContext(type [,arguments])
グラフィック描画を行うためのコンテキストオブジェクトを取得する. 引数の内容によって得られるコンテキストオブジェクトの種類が変化する. なお, 誤ったキーワードを指定した場合, ブラウザが対応する機能を持たない場合はnullが返される.
2d…CanvasRenderingContext2D,
webgl/experimental-webgl…WebGLRenderingContext,
bitmaprenderer…ImageBitmapRenderingContext
コンテキストオブジェクトはcanvas要素につき唯一である.
第2引数にはコンテキストオブジェクトに対するパラメータ(オブジェクト形式)を指定する.
getContextメソッドに渡すパラメータ一覧
種類キー意味
2dalphatrue/falseアルファチャンネルの有効/無効
storagepersistent/discardable(blinkのみ)コンテキストの強制開放の有無→ブラウザ環境の保護
webglalpha/depth/stencil/antialias/premultipliedAlpha/preserveDrawingBuffer/failIfMajorPerformanceCaveat
※ブラウザが独自のAPIを定義しても良い.
HTMLCanvasElement.probablySupportsContext(type [,arguments])
指定したコンテキストタイプをブラウザがサポートしているかを判定する.

getContextメソッドによるコンテキストオブジェクトの取得

canvas要素内部のグラフィックにアクセスするために, まずgetContextメソッドを実行しコンテキストオブジェクトを取得する. コンテキストオブジェクトには複数種あり, 用途や目的によって使い分けることが出来る. 引数に与えたキーワードによって得られるオブジェクトの中身が変化する. それぞれが独自のAPIを備えているため互換性はなく, 個々にその内容を憶える必要がある. なお, HTMLCanvasElementが実装している機能(例えばtoBlobメソッド)はコンテキストオブジェクトの種類に依らず利用できる.

この他, ブラウザごとに独自のコンテキストオブジェクトを定義しても良いこととなっている. なお, キーワードに対応するコンテキストをサポートしない場合, null値が返される.

コンテキストオブジェクトからcanvas要素を参照する

コンテキストオブジェクトのcanvasプロパティにはこのコンテキストを生成したcanvas要素が設定されている.

ctx.canvas
このコンテキストを生成したcanvas要素.

レンダリング方式の確定

canvas要素がどのレンダリング方式で描画されるかについてはgetContextメソッドの初回呼び出し時に決定される. 2度目以降に初回と別のキーワードを指定した場合, nullが返される.

コンテキストオブジェクトの唯一性

getContextメソッドで得られるコンテキストオブジェクトはcanvas要素につき唯ひとつであるため, 下のようにコード上は2つのオブジェクトが存在しているように見えても実体は全く同じものとなる.

アルファチャンネルの無効化

通常canvas要素ではアルファチャンネル(不透明度)を含めたグラフィックの描画を行うが, getContextメソッドの第2引数に「{alpha: false}」を指定するとこのアルファチャンネルを無効とすることができる. この場合, カンバスは黒(rgb(0,0,0)/rgba(0,0,0,255))で初期化される.

生成する画像に透明部が必要ない場合は, このパラメータを明示することで描画処理速度の向上が見込める.

canvas要素における2つのサイズ

canvas要素にはその仕組み上, グラフィックそのもののサイズとグラフィックの見た目のサイズの2つのサイズが定義される.

canvas要素に定まる2つのサイズ
 グラフィックそのものサイズグラフィックの見た目のサイズ
対応する記述width/height属性CSSのwidth/heightプロパティ
記述例<canvas width="200" height="200"></canvas><canvas style="width:200px; height:200px;"></canvas>
未設定時の扱い300×150グラフィックそのものサイズ

width/height属性を指定しなかった場合の動作

canvas要素にwidth属性もしくはheight属性が指定されていなかった場合, 往々にして意図した出力結果とならない. 例えば次の例ではCSSで見た目のサイズを200px×200pxに設定しており, そのサイズでグラフィックを青色で塗りつぶそうとしているが, 実際には300×150のカンバスに描いているため描画結果が狂っている.

グラフィックの見た目のサイズにグラフィックそのもののサイズを合わせる

画像サイズを予め固定出来ない場合, window.getComputedStyleメソッドを利用すると, グラフィックの大きさをcanvas要素の見た目の大きさに合わせることが出来る.

これを応用すると, canvasグラフィックの大きさに比率値を指定 = document.querySelector("canvas");することも出来る. 但しウインドウのリサイズ時にグラフィックの再描画を行うなどの工夫が必要となるだろう.

DOM操作とcanvas要素

canvas要素に描画した内容は, canvas要素そのものの変更を伴うDOM操作の影響を受ける. なお, canvas要素の配置の変更と言った操作の影響は受けない.

widthプロパティ,heightプロパティに対する操作

canvas要素に対するwidth/heightプロパティを操作するとグラフィック全体が初期化される.

cloneNodeによるcanvas要素の複製

HTMLCanvasElementとして得たcanvas要素はcloneNodeメソッドにより複製できるが, canvas要素に描いた内容までは引き継がない.

canvas要素を含む要素のinnerHTMLプロパティを操作する

この場合暗黙的にcanvas要素が再構成されるため, 内容がクリアされてしまう.

出典:innerHTML clears the drawing canvas pixels.

この場合, 代替としてinsertAdjacentHTMLメソッドを用いると良い. 既存のDOM構成が維持されるため, canvasグラフィックが残る.

環境の確認

現在では少なくなったが, canvas要素に対応していないブラウザにおいてはこれまでのコードはエラーとなってしまう. 従って何らかのトラップコードを実装しておき, 処理の振替を行うようにしておくと良い. この場合CanvasRenderingContext2Dの実装状況を確認するようにする.

CanvasRenderingContext2Dの拡張

canvasを利用するにあたり, よく使う処理は共通ライブラリとして使い回したい. その際, CanvasRenderingContext2Dオブジェクトそのものを拡張してしまう方法がある. 例を示す.

事前にCanvasRenderingContext2D.prototypeオブジェクトに関数を定義しておくことで, getContextメソッドで取得したコンテキストオブジェクト全てで利用可能となる. 同様にCanvasRenderingContext2Dがもつメソッドそのものを拡張することもできる. 下の例ではarcメソッドの角度の指定を省略した場合に円を引くように機能を拡張したものだ.

3・図形の描画

canvas要素における座標系

まずはじめに図形を描画する上で最も基本となる座標系の考え方について示す. canvas要素における座標は左上が(0,0)であり, 右下が(width,height)となり, 単位はピクセルとなる. この範囲から外れた描画は無視される.

基本的な描画処理の流れ

CanvasRenderingContext2Dオブジェクトを用いたグラフィックの描画の流れは次の通りである.

  1. beginPathメソッドにより描画範囲の開始を宣言する. なお省略すると暗黙的に実行されたこととなる.
  2. moveToメソッドによりパスの起点を指定する.
  3. lineTo, arcTo, quadraticCurveTo, bezierCurveToメソッドによりパス切片を追加していく.
  4. 必要に応じclosePathメソッドを実行してパスを閉じる.
    これらのメソッドを必要な回数繰り返す. これらの図形は次にbeginPathメソッドが呼び出されるまで内部に保持される.
  5. コンテキストオブジェクトにスタイルを設定する.
    これは下記のメソッドにおける色や形の指定, 画像合成の方法を行うものである.
  6. strokeメソッド, fillメソッドを用いてパス図形を描画する.
    現在コンテキストが内部に保持しているパス図形に沿って線の描画, 領域の塗り潰しが行われる. なおこれらの描画処理は画像領域に上書き(合成)していくので, 元の位置に既にグラフィックが存在した場合に描画処理を取り消すことはできない.

この処理を1セットとして描きたい図形の分だけ繰り返すこととなる. 例を示す.

CanvasRenderingContext2D.beginPath()
パス図形を初期化し, パス図形の開始を宣言する.
CanvasRenderingContext2D.fill([path2d],[fillRule])
現在のパス領域を塗りつぶす. Path2Dをサポートしている場合, 引数に渡したパスデータの領域を塗りつぶす. fillRule…塗り潰し方法の指定 nonzero/evenodd
CanvasRenderingContext2D.stroke([path2d])
現在の領域の境界に線を引く. Path2Dをサポートしている場合, 引数に渡したパスデータの領域を塗りつぶす.

beginPathメソッド実行すると, それまでのパス図形を破棄し新たなパス図形を設定する. このメソッドを実行せず図形の描画処理を行った場合, 暗黙的に呼び出したものとして扱われるため, どこから新しいパス図形かがわからなくなる.

このようにcanvasでの描画処理は逐次上に図形を重ねがけしていく点が特徴であり, 一部の描画の内容を変更する(例えばグラフィックを動かす)と言った場合は処理を最初からやり直す必要がある. なおこれは古典的なcanvas要素の使い方であり, 下で紹介するPath2Dオブジェクトを利用すると煩雑なパス図形定義処理をオブジェクトとして管理することが可能となる.

塗り潰しの順番

strokeによる境界線の上に塗り潰しを施すことでHTMLにおけるborder的な描画を行うこともできる. 先ほどの例のstrokeメソッドとfillメソッドを実行する順番を交換したものを示す.

パスの描画

CanvasRenderingContext2Dオブジェクトには以下に示すパス切片を定義するための4つのメソッドが提供されている. これらのメソッドを数珠つなぎで実行することで一連なりのパス図形を表す. 予めmoveToメソッドで起点を指定し, 各メソッドを実行すると引数として与えた終点座標まで線が引かれる. この終点が次のメソッドにおける始点となる. 注意すべき点は, これらのメソッドは単にパス図形を定義するだけであり, 線の色や塗り潰しなどのグラフィックの見た目は別のメソッド(stroke, fillメソッド)を用いる.

CanvasRenderingContext2D.moveTo(x,y)
パス切片の起点を指定する. x,y…起点の座標
CanvasRenderingContext2D.lineTo(x,y)
直線を引く.
CanvasRenderingContext2D.arcTo(x1,y1,x,y,radius)
円弧を引く. x1,y1…頂点の座標, x,y…終点の座標, radius…円弧の半径
CanvasRenderingContext2D.quadraticCurveTo(x1,y1,x,y)
2次ベジェ曲線を引く. x1,x2…ベジェ曲線の制御点座標, x,y…終点の座標
CanvasRenderingContext2D.bezierCurveTo(x1,y1,x2,y2,x,y)
3次ベジェ曲線を引く. x1,y1…始点に対する制御点, x2,y2…終点に対する制御点座標, x,y…終点の座標
CanvasRenderingContext2D.closePath()
パス切片を閉じる.

起点の指定:moveTo

各種パス切片を定義する際の始点を指定するメソッドである. 直線にしろベジェ曲線にしろ, まずはこのメソッドで始点を指定する必要がある.

直線:lineTo

最も基本的なメソッド. 前回の終点から引数に指定した終点まで直線を引く. 繰り返し実行することで多角形を表現することが出来る.

角の丸め:arcTo

2直線に接する円弧を描画する. 引数としては有向線分の端点座標2つと円の半径を与える. こうすると現在位置-始点(直線1)と始点-終点(直線2)に接する円が求まる. そこで, arcToは現在位置からまず円の接点まで線を引く. 続いてもう片方の接点まで円弧を引き処理を終了する.

このメソッドは角丸四角形を定義する場合に便利である.

2次ベジェ曲線:quadraticCurveTo

制御点と終点を与え2次のベジェ曲線を引く.

3次ベジェ曲線:bezierCurveTo

始点と終点に対する制御点2つを与え3次のベジェ曲線を引く.

パスを閉じる:closePath

closePathを実行すると直近のmoveToメソッドで指定した座標まで直線が引かれ, 図形が閉じられる.

逆にclosePathメソッドを指定しなかった場合, 開いた図形となる. fillメソッドはclosePathを実行した際の領域を塗りつぶす.

名称が対照的であるため勘違いしやすいが, beginPathメソッドとclosePathとの間に直接的な関連は存在しない.

矩形と円弧の定義

先程の4つのメソッドでほとんどの図形は定義可能だが, よく使う矩形(四角形)と円弧の定義については専用のメソッドが提供されている. どちらも, (beginPath〜)moveTo〜closePathまでの一連の操作を一括で指定できるようにしたマクロ的な動作をするため, パス図形の中に組み入れて利用することが出来る.

CanvasRenderingContext2D.rect(x,y,w,h)
矩形の生成. x,y…矩形の左上頂点の座標, w…矩形の幅, h…矩形の高さ
CanvasRenderingContext2D.arc(cx,cy,radius,startAngle,endAngle,anticlockwise)
円弧の生成. cx,cy…円弧の中心座標, radius…円弧の半径, startAngle,endAngle…円弧の開始/終了ラジアン, anticlickwise…反時計回りフラグ

矩形:rect

rectメソッドを利用すると, 4頂点(つまり8数値)を指定して定義していた四角形を左上の頂点と幅と高さの4つの値で指定することが可能となる. rectメソッドの終点は左上の頂点となるので, 後続のlineToメソッド等はこの頂点を基準に線を描画する.

rectメソッドで引かれるパスは時計回りとなる.

width値やheight値を負の値を指定した場合は反時計回りとなる.

円弧:arc

arcメソッドでは円/円弧を定義する. また扇形を定義することが可能である. 中心の座標と半径, 円弧の開始角と終了角(単位ラジアン)を指定する. 従って真円を描く場合は角度に0〜Math.PI*2を指定し, closePathメソッドでパスを閉じる. 角度の基準は中心から右のラインを0度とし, 時計回りに角度を算出する.

anticlockwiseフラグにtrueを指定すると, 円弧の定義を反時計回りに行う.

扇形を定義する場合は, 一旦円弧の中心にmoveToメソッドで起点を移し, arcメソッド実行後closePathメソッドを実行すると良い.

明示的にbeginPathメソッドが指定されていない場合, arcメソッドは暗黙的にbeginPathを実行し, 始点を円弧の開始点とするが, 他のサブパス生成処理の後にarcメソッドを実行すると円弧の始点まで勝手に直線を定義してしまう. これは先程の扇形の定義を行うには便利な機能だが, 場合によっては煩わしい. このような場合はmoveToメソッドで円弧の開始点にパスの起点を移動させてしまうとよい. 複数の円弧を単一のパス図形として扱う場合に注意しよう.

このようにarcメソッドに依る図形の定義は制御が難しい面もあるため, 事前にbeginPathメソッドを実行しておき, 他のパス図形と分離した方が見通しが良い.

楕円弧:ellipse

ellipseメソッドは楕円・楕円弧を定める. 使い方はarcメソッドと同様だが, 半径の代わりにx軸方向の半径とy軸方向の半径が指定可能で, 楕円の傾きが追加されている.

CanvasRenderingContext2D.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
楕円弧の生成. cx,cy…楕円弧の中心座標, radiusX,radiusY…楕円弧のx軸/y軸方向半径, rotation…楕円弧の回転角, startAngle/endAngle…楕円弧の開始/終了ラジアン, anticlockwise…反時計回りフラグ

パスの中抜き

単一のパス図形のパス切片が重なっていて, そのパス切片の向きが互いに逆向きであった場合, 重なっている部分は塗り潰しの対象外となる. 例を示す.

CanvasFillRuleの指定

fillメソッドの引数としてCanvasFillRuleを指定することで上記塗り潰しの方法を切り替えることが出来る. 先程の例に引数「evenodd」を追加すると偶数回重なっている部分が繰り抜かれる.

nonzero
規定値. パスで囲まれている部分が塗りつぶされる.
evenodd
重なっている部分が奇数回の部分が塗りつぶされる.

Path2Dオブジェクトによるパス図形の管理

canvas要素においてパス図形を管理することはこれまで難しかったものの, 新たに追加されたPath2Dオブジェクトを用いることでこの問題が解決する.

Path2D()
パス図形を表すオブジェクト. 引数として既存のPath2DオブジェクトPath2Dオブジェクトの配列,fillruleSVGパスデータ文字列の何れかを指定できる.

下の例では, SVGのパスデータ文字列を使ってPath2Dオブジェクトを生成して使いまわしている.

このように図形そのものをオブジェクトとして扱うことが出来るため, 再利用や編集が非常に容易に行えるようになった.

Path2Dオブジェクトのもつメソッド

Path2DオブジェクトにはCanvasDrawingContext2Dオブジェクトのもつパス図形定義関数「closePath,moveTo,quadraticCurveTo,besierCurveTo,arcTo,rect,arc,ellipse」の全てが定義されている. 従って既存のコードをPath2Dを使って書きなおすことは容易に出来る. 先ほどの例をPath2Dを使って書き換えてみよう.

ctxに対して発行していたメソッドの対象がpathに変更された点とfillメソッドの引数にpathが追加されている他は全く変わらない.

SVGのパス文字列を用いたPath2Dの生成

SVGではパス図形をパスデータ文字列と呼ばれる形式で定義する. Path2Dオブジェクトの生成にはこの文字列を利用することが出来る. パスデータ文字列を構成するコマンドの意味合いは次の通り.

SVGのパスコマンド一覧
コマンド内容記述対応するコード
M/m始点に移動するM[x],[y]ctx.moveTo(x,y)
L/l直線を引くL[x],[y]ctx.lineTo(x,y)
H/h水平線を引くH[x]ctx.lineTo(x,[現時点のy座標])
V/v垂直線を引くV[y]ctx.lineTo([現時点のx座標],y)
C/c3次ベジェ曲線を引くC[x1],[y1] [x2],[y2] [x],[y]ctx.bezierCurveTo(x1,y1,x2,y2,x,y)
S/s3次ベジェ曲線を引くS[x2],[y2] [x],[y]ctx.bezierCurveTo([自動計算],x2,y2,x,y)
Q/q2次ベジェ曲線を引くQ[x1],[y1] [x],[y]ctx.quadraticCurveTo(x1,y1,x,y)
T/t2次ベジェ曲線を引くT[x],[y]ctx.quadraticCurveTo([自動計算],x,y)
A/a楕円弧を引くA[rx],[ry] [angle] [largeArcFlag] [sweepFlag] [x],[y]対応する関数なし
Z/zパスを閉じるZctx.closePath()
小文字のコマンドは現在位置からの相対座標として扱います.

先程の例を今度はパスデータ文字列を使って書き直したものだ.

このように上手く使いこなせれば大幅にパス定義処理の記述を減らすことが可能となる.

Path2D専用のメソッド

この他にもグラフィックを描く上で便利な機能が定義されている.

Path2D.addPath(path, transform)
パス図形に他のパス図形を追加する. transformにはSVGMatrix/DOMMatrixを渡す

4・塗りと線のスタイル

スタイル設定

パス図形を定義した後, strokeメソッドで線を, fillメソッドで塗り潰しが行われることはここまで見た通りだ. その際に線の太さや色等の様々な設定を行うためのプロパティが提供されている.

CanvasRenderingContext2D.strokeStyle
線のスタイル. 色の他, グラデーション・パターンを指定する.
CanvasRenderingContext2D.fillStyle
塗り潰しのスタイル. 色の他, グラデーション・パターンを指定する.
CanvasRenderingContext2D.lineWidth
線の太さ.
CanvasRenderingContext2D.lineCap
線の端点のスタイル. butt(無し)/round(丸め)/square(四角)
CanvasRenderingContext2D.lineJoin
線の頂点のスタイル. bevel(面取り)/round(丸め)/miter(尖り)
CanvasRenderingContext2D.miterLimit
lineJoinがmiterの場合の尖りの限界値.

グラフィックの色:strokeStyle, fillStyle

strokeStyleは線の色を, fillStyleはパス図形の塗り潰しの色を表す. 色の指定にはHTML色rgb関数/rgba関数#000/#000000 色のHEX(16進)指定hsl関数/hsla関数currentcolorの何れかを指定する(初期値は黒). 不透明度(rgbaにおける4つめの値, alpha. 0で透明, 1で不透明となる. )が指定されている場合は, その内容に応じて色が重ねられる.

fillStyle及びstrokeStyleプロパティは値を参照することも出来る. 値を参照した場合, HEX値(不透明度が指定されていた場合はrgba形式)での色文字列が返される.

線の太さ:lineWidth

lineWidthには線の太さをピクセル単位で指定する. canvas要素では線の中央がパス図形と重なるように線が描かれる.

なお, 線の境界がピクセル境界に合っていない場合や幅が1ピクセルに満たない場合は, 自動的にアンチエイリアスが掛かる. そのため, 出力結果の色が期待していたものと異なると言った現象が起こる.

パス図形を固定して異なる描画スタイルを重ねる

単一のパス図形に対してスタイルを変更し, stroke/fillメソッドを続けて実行することで図形を重ねることが出来る.

同様に, パス図形に関わる処理を連続実行することも出来る.

currentcolorの効能

「currentcolor」をfillStyle/strokeStyleプロパティに指定すると, 値を設定したその時点でのcanvas要素におけるcolorプロパティの色を設定する.

colorプロパティは祖先要素から継承されるため, canvas要素のプロパティが未指定であっても値をもつ場合がある.

lineCap

lineCapプロパティは端点のスタイルを表す. butt, round, squareの何れかから選択する.

lineJoin

lineJoinプロパティは頂点のスタイルを表す. bevel, round, miterの何れかから選択する.

lineJoinプロパティがmiterの場合, miterLimitプロパティで尖りの限界値を設定することが出来る.

破線の描画

strokeで描く線を破線とすることができる. setLineDashメソッドには引数として破線のパターンを配列として渡す. 破線のパターンは[実線,間隔(,実線,間隔...)]として記述する. lineDashOffsetは破線の開始位置をマイナス方向にずらす.

CanvasRenderingContext2D.setLineDash(segments)
破線の設定をする. segments…破線設定の配列
CanvasRenderingContext2D.getLineDash()
破線の設定を配列として取得する.
CanvasRenderingContext2D.lineDashOffset
破線のオフセット値を設定する.

矩形領域に関わる専用メソッド

矩形範囲に対する操作は頻出するため, 専用のメソッドが提供されている. これらの操作はパス図形の定義とは関連せず, 独立して実行することが出来る.

CanvasRenderingContext2D.fillRect(x,y,width,height)
現在のコンテキストの設定を使って矩形領域を塗りつぶす. x,y…矩形の左上頂点の座標, width…矩形の幅, height…矩形の高さ
CanvasRenderingContext2D.strokeRect(x,y,width,height)
現在のコンテキストの設定を使って矩形の線を描画する. x,y…矩形の左上頂点の座標, width…矩形の幅, height…矩形の高さ
CanvasRenderingContext2D.clearRect(x,y,width,height)
指定した矩形をクリアする. x,y…矩形の左上頂点の座標, width…矩形の幅, height…矩形の高さ

矩形領域の塗り潰し:fillRect

矩形領域の線:strokeRect

矩形領域のクリア:clearRect

canvasは初期の色として全面が「黒の透明(rgba(0,0,0,0))」で初期化されている. clearRectメソッドは指定した矩形範囲の描画の内容をこの黒の透明に初期化する. 下の例では分かりやすくするためcanvas要素のbackground-colorをyellowに指定している. clearRectメソッドを実行したことで, グラフィックをクリアした部分の背景の色が透けて見えている.

カンバス全体のクリア方法

カンバスグラフィックの内容をクリアするには主に次の4つの方法が考えられる.

  1. ctx.clearRect(0,0,canvas.width,canvas.height);
    (推奨)コストの低い操作であるため, 特に問題がない限りこの方法を選択したい. 最も単純なクリアの方法だが, 座標軸変換が存在している場合に正しく動作しない.
    この問題はステートのセーブで解決するが, 肝心の記述量が増えてしまう.
  2. canvas.width = canvas.width;
    簡単に記述できるが, グラフィックの他スタイル履歴情報等も全てクリアされる. 明示的に何を意図しているのか判りにくいのが欠点.
    ※Internet Explorerではパス図形のみ維持されるといったようにブラウザ間で動作に違いがある.
  3. ctx.globalCompositeOperation = "copy"; ctx.rect(0,0,0,0); ctx.fill();
    画像合成を逆手に取った画像のクリア. 座標軸変換等の状態などを気にせずともよく意外に使いやすい. 但しcanvas全体に演算が走ることがあり, Firefoxではコストの高い操作となる.
  4. ctx.putImageData(ctx.createImageData(canvas.width, canvas.height), 0, 0);
    (非推奨)グラフィックデータのみをクリアできる. origin-cleanフラグがfalseとなっていた場合にエラーが発生しうる. なお生成したImageDataオブジェクトは使い捨てにせず変数にとっておき, クリアの都度再利用するとよい.

図形描画とアンチエイリアス処理

canvas要素を構成する画素の数は有限であるため, そこに(無限の解像度を持つ)ベクタ図形を描画する上で自ずと近似(アンチエイリアス)処理が発生する. Retinaディスプレイ等の高解像度環境では目立ちにくいものの, 時に厄介な問題を引き起こします.

線の境界のアンチエイリアス処理を回避する

strokeによる線の描画は図形の境界が線の中央となるように描画されるが, 設定によっては美しい結果が得られない. 例えば下の図においてパス図形が黄色の線だとすると, 1px幅のストロークは2ピクセルにまたがってしまうためアンチエイリアスが発生する.

何も対処しなかった場合 何も対処しなかった場合 幅を2の倍数とする場合 幅を2の倍数とする場合 描画位置を0.5ずらす場合 描画位置を0.5ずらす場合

このような場合は, lineWidth値を2の倍数としたり, 線の位置を0.5ピクセルずらすといった作業を行うと改善される. なお, これはcanvas要素にスタイルが設定されていたり, ディスプレイの解像度によって変化するため制御が難しい.

図形の一括描画と逐次描画の違い

同様の問題はfillメソッドに依る塗り潰しにおいても発生する. 同じ領域に同じ色を重ねた場合, 論理的には全く同じ結果となるはずだが, パス図形の境界においてアンチエイリアス処理が発生すると, 塗り潰し処理を一括で行うか否かで得られる結果が異なってしまう. 逐次塗り潰しを行った場合, 不透明部が重ね合わされる(アルファマルチプリケーション)ことで, 想定した色よりも濃い部分が発生する.

描画スタイルをCSSから参照する

描画スタイルの中にはCSSの設定内容を流用可能なものもある. 例えばフォントスタイルをCSSから取得すると, HTML文書内のテキストとcanvasグラフィック内部のテキストの見た目を統一することが出来る. canvas要素に現在適用されているスタイルを取得するにはwindow.getComputedStyleメソッドを用いる.

window.getComputedStyle(element)
指定した要素の現在のスタイル設定情報を取得する.
/*スタイルシートの記述*/ canvas#styledcanvas{ fill: orange; stroke: pink; stroke-width: 15; stroke-dasharray: 25,25; stroke-linejoin: round; font:bold 25px serif; }

以下にcanvasへ流用可能なCSSプロパティのうち, 比較的単純に利用できるものを示す.

canvasへ流用可能なCSSプロパティ
CSSプロパティstyleプロパティ対応するctxプロパティ内容・備考
widthwidth(width)canvasのサイズとする.
正確にはbox-sizingの設定を参照する必要がある.
heightheight(height)
fillfillfillStyle塗りの色†
strokestrokestrokeStyleストロークの色†
stroke-widthstrokeWidthlineWidthストロークの幅†
「px」を取り除く.
stroke-linecapstrokeLinecaplineCapストローク端点の形状†
stroke-linejoinstrokeLinejoinlineJoinストローク頂点の形状†
stroke-miterlimitstrokeMiterlimitmiterLimitストローク頂点の尖りの限界†
stroke-dasharraystrokeDasharraysetLineDash破線のスタイル†
「px」を取り除き, 「,」で分割して利用する.
font-stylefontStylefontフォント設定
内容をスペースで連結する.
font-variantfontVariant
font-weightfontWeight
font-sizefontSize
font-familyfontFamily

5・グラデーションとパターン

グラデーションの生成

CanvasGradientオブジェクトを用いることで, 塗り潰しや線にグラデーションを設定することが出来る. CanvasGradientを生成するメソッドとしてはcreateLinearGradientとcreateRadialGradientの2つが提供されている.

CanvasRenderingContext2D.createLinearGradient(x0,y0,x1,y1)
線形グラデーションオブジェクトを生成する. x0,y0…グラデーションの開始座標, x1,y1…グラデーションの終了座標
CanvasRenderingContext2D.createRadialGradient(x0,y0,r0,x1,y1,r1)
放射状グラデーションオブジェクトを生成する. x0,y0,r0…グラデーションの開始円, x1,y1,r1…グラデーションの終了円
CanvasGradient.addColorStop(offset, color)
グラデーションの色の起点を設定する. offset値は0〜1の間で指定する. この範囲外の値を指定した場合エラーとなる.

線形グラデーション:createLinearGradient

createLinearGradientメソッドは引数としてグラデーションの始点と終点の合わせて4つの値を取る. 得られたオブジェクトにはaddColorStopを指定することでグラデーションの基準となる色を設定することが出来る. この指定方法はSVGにおけるグラデーションの指定方法に近く, CSS3での角度による指定方法とは使い勝手が異なる点に注意する.

得られたCanvasGradientオブジェクトは通常の色文字列と同様にfillStyleプロパティ, strokeStyleプロパティに設定することが出来る.

colorStop値を設定する順番に特に決まりはないが, 同じoffset値が色の境界を表すという特徴からなるべくoffset値が小さいものから記述するようにすると見通しが良い.

放射状グラデーション:createRadialGradient

createRadialGradientメソッドは引数として2つの円(中心座標+半径)を取る. colorStop値の設定は線形グラデーションと同様である.

円の中心をずらすことで様々な表情のグラデーションを表現することが可能となる. なお, 色の境界がギザギザしていて気になる場合は, offset値をほんの少しずらすことで気になりにくくなる.

なおSVGやCSS3におけるグラデーションの繰り返しと言った機能は存在しない.

パターンの生成

グラデーションと同じような役割を果たすものとしてpatternがある. これは図形の塗り潰しや線の描画といったものに図案を指定可能とするものである.

CanvasRenderingContext2D.createPattern(image,repetition)
パターンオブジェクトを生成する. image…パターン画像, repetition…パターン繰り返しの方法.
repeat(両方向(初期値))/repeat-x(水平方向のみ)/repeat-y(垂直方向のみ)/no-repeat(なし)

パターンの生成:creataPattern

元となるパターン画像としてはimg要素の画像canvas要素の画像video要素の映像の何れかを利用する. 例えばパターン画像としてを利用する場合, 次のような結果が得られる.

このようにhtml文書に表示されている要素を利用する場合は問題ないが, 動的に画像を読み込む場合は注意すべき点が存在する. これは後ほど示す.

また動的にcanvas要素を生成し, それをパターンとして利用することも出来る.

この方法をstrokeに応用することで破線を描画することもできる. しかし, 曲線に対応するのが面倒であるなど使い勝手は今ひとつである.

パターンに対する変形(tobe)

塗り潰しパターンに変形処理を施す為のメソッドが検討されている. なお, 現状でも座標軸の変形処理と組み合わせれば実現可能である.

CanvasPattern.setTransform()
パターンに変形を施す.

6・画像効果・合成

クリップによる描画領域の制限

clipメソッドを実行すると, コンテキストオブジェクトに保持していたパス図形をクリップ領域に変換し, それ以降のカンバスへの描画をこの範囲に制限することが出来る.

CanvasRenderingContext2D.clip([path2d],[fillRule])
現在のパス図形の範囲をクリップ領域に変換し, fill, stroke, drawImage, fillRect, strokeRect, clearRectメソッドによるこの範囲外への図形・画像の描画を無効とする. Path2Dオブジェクトをサポートしている場合は引数渡したパス図形オブジェクトでクリップする. fillRule…クリップの方法

clipメソッドは重ねがけすることが出来る. 重ねる毎にクリップ領域が狭まる.

任意形状の繰り抜き

clip領域はclearRectによるグラフィッククリアにも適用される.

クリップ領域の解除

一度クリップ領域を定義した場合原則元に戻すことはできないが, 事前にsaveメソッドを実行しておくとrestoreメソッドを実行することでsaveメソッドを実行した時点でのクリップ領域に戻すことが出来る.

ドロップシャドウ効果

shadowプロパティに値を設定しておくことで, 図形描画時に影を描くことが可能となる. 影は塗り潰し, 線の描画の両方に有効である. その際のスタイルに不透明度が設定されている場合は, 背後に影が描画されているものとして図形がその上に合成される.

CanvasRenderingContext2D.shadowColor
影の色.
CanvasRenderingContext2D.shadowBlur
影のぼかし幅.
CanvasRenderingContext2D.shadowOffsetX
影のx軸方向のずらす量.
CanvasRenderingContext2D.shadowOffsetY
影のy軸方向のずらす量.

なおshadowColorにグラデーションを指定することはできない.

影のみを描画する

影のみを描画するには, 元となるパス図形をカンバスの範囲外に定義し, offset値を使って影をグラフィック範囲内に引き込むようにする. これは単色の図形に対するぼかし処理を単純化する.

パス図形の内側への影を定義する

出典:Inset shadows with HTML5 Canvas

canvasでの影は通常図形の外側に広がるように定義されるが, CSSでのbox-shadowプロパティではinsetオプションを設定することで図形範囲の内側に広がる影を描くことが出来る. この動作をcanvasで再現する場合は, 次のように元となる図形を逆方向の矩形で囲み, 図形を反転させてから影をつけるようにする.

不透明度とグローバルアルファ

図形の不透明度を指定するには2つの方法があって, 色の指定をrgba関数で行うglobalAlphaプロパティで透明度を指定するの2つの方法がある.

CanvasRenderingContext2D.globalAlpha
共通不透明度. 描画処理にたいする不透明度を表す. 0で完全に透明. 1で不透明. なお, 透明であっても色の情報は存在している点に注意する.

色の指定にrgba関数を用いた例を示す.

これをglobalAlphaプロパティで書き換えたものが次である.

globalAlphaの値は何も単色の指定にのみ有効というわけではない. 例えば外部画像をカンバスに出力する場合の透明度としても利用できる.

画像の合成

複数の図形・画像を合成する際, その合成方法をglobalCompositeOperationで指定することが出来る. この値は既存の色に新たな色を付け加える際の計算式を表し, clipメソッドによるクリップ領域を設定せずとも画像のくり貫き等を実現することができる.

CanvasRenderingContext2D.globalCompositeOperation
画像の合成方法. 下記のcomposite-modeかblend-modeの何れかを指定する. 詳しい処理内容については仕様を参照のこと.
composite-mode
source-atop
A atop B
source-in
A in B
source-out
A out B
source-over
A over B(初期値)
destination-atop
B atop A
destination-in
B in A
destination-out
B out A
destination-over
B over A
lighter
A plus B
copy
A(B is ignored)
xor
A xor B
blend-mode(Compositing and Blending Level 1)
normal
通常(source-overに同じ)
multiply
乗算
screen
スクリーン合成
overlay
オーバーレイ合成
darken
比較暗
lighten
比較明
color-dodge
覆い焼き
color-burn
焼きこみカラー
hard-light
ハードライト
soft-light
ソフトライト
difference
差の絶対値
exclusion
除外
hue
色相
saturation
彩度
color
カラー
luminosity
輝度
 
 

ドロップシャドウ処理は内部的にこの画像合成機能を利用しているため, 設定値を変更してしまうことで影の描画処理に影響を及ぼす点に注意しよう.

以下に例を示す. 先に赤い円を描画し, その後に青い矩形を描画する. その際の描画結果の違いについて注意して欲しい. 詳しくはこちらを参照のこと.

source-atop

元画像に重なった部分のみを描画する.

source-in

元画像をクリアし, 元画像に重なった部分のみを描画する.

source-out

元画像をクリアし, 元画像に重なっていない部分のみを描画する.

source-over

元画像に新たな画像を重ね合わせる. 何も宣言しなかった場合の初期値.

destination-atop

先ほどのsource-atopと立場を逆転した描画を行う.

destination-in

先ほどのsource-inと立場を逆転した描画を行う.

destination-out

先ほどのsource-outと立場を逆転した描画を行う.

destination-over

先ほどのsource-overと立場を逆転した描画を行う.

lighter

重なった部分のrgbの画素毎に明るい(値の大きい)ものを用いる. 不透明度による画像の重ね合わせが全体として暗くなるのに対し, ligter合成では逆に明るくなる.

copy

元画像を透明にして図形を重ねてしまう.

これは, 現在の描画状況を平行移動する際に利用する.

xor

元画像との共通部分がクリアされる.

darker

webkit専用の合成メソッド. 同等のblend-modeであるdarkenと同等の結果を得る.

blend-modeの指定

Compositing and Blending Level 1をサポートしている環境ではglobalCompositeOperationに上記のcomposite-modeの他にblend-modeを指定することが出来る.

色成分への分解と再構成

globalCompositeOperationを用いると画像を成分毎に分解可能である. また, 分解した色情報を再度合成することで元の画像を復元することもできる.

RGB

光の三原色による分解・合成

A

不透明度の抽出.

CMY

色の三原色による分解・合成.

HSL

blend-modeのうち, hue, saturation, luminosity, colorはいずれもHSL色空間に対する操作を表す. 操作毎の色相, 彩度, 輝度値の対応を図にすると次のようになる. この値の対応を元に画像をh,s,l値に分解し, 再度合成し直すことができる.

フィルタ

フィルタとは画像に対して何らかの効果をもたらすもので, 代表的なものとしてはグレイスケール化やセピア化と言ったものが挙げられ, canvas要素が描くグラフィックにおいても様々なフィルタを利用することが可能である.

WEBブラウザ環境においては様々な方法で画像にフィルタをかけることができるが, 主に「可逆」なものと「不可逆」なものに分けられる. 前者はCSS Filter/SVG Filterとして知られており, 画像そのものには手を入れず, スクリーンに描画する過程でフィルタを適用するものだ. 本項で解説するfilterプロパティによるフィルタはcanvas画像そのものを改変するため, 不可逆なフィルタと言える.

CanvasRenderingContext2D.filter
これから描画する内容に対するフィルタのフィルタ関数を指定する.

コンテキストオブジェクトのfilterプロパティにフィルタ関数を指定すると, 以降のグラフィック描画にフィルタ効果が適用される. これはfilterプロパティの内容が変更されるまで有効となる. なお, canvas要素に描画されている内容に対してフィルタを掛けているわけではない.

canvasの描画内容にフィルタを適用したい場合は, 一旦ImageBitmapや別のcanvas要素に現在の描画内容を転写しておき, filterを設定後に書き戻すようにする.

指定可能なフィルタ関数には次のものがある. なおフィルタ関数を列挙することで複数のフィルタを重ねがけすることも出来る.

7・テキストの描画

fillTextメソッドとstrokeTextメソッド

canvas要素においてテキストを描画するにはstrokeTextメソッドもしくはfillTextメソッドを用いる. フォントの指定はfontプロパティにて行うが, strokeStyleやfillStyleはこれまでと同様に行える. また, テキスト描画の最大幅を設定することで文字列が描画範囲からはみ出さないようにすることが出来る. なお描画は一行のみで, 複数行に渡る描画は行えない.

CanvasRenderingContext2D.strokeText(text,x,y[,maxWidth])
文字列のアウトラインを描画する. テキストと描画位置, 最大の幅を指定する.
CanvasRenderingContext2D.fillText(text,x,y[,maxWidth])
文字列を塗りつぶして描画する. テキストと描画位置, 最大の幅を指定する.
CanvasRenderingContext2D.font
フォントの設定. 書式はCSSにおける記述に準ずる. つまり, 下記の値を順に記述する.
font-style
フォントのスタイル. [normal|italic|oblique].
font-variant
フォントの見た目. [normal|small-caps].
font-weight
フォントの太さ. [normal|blod|100〜900].
font-size
フォントの大きさ.
font-family
フォントの種類.

例を示す. 文字列の最大描画幅が指定されていた場合, 文字列の調整方法として, 文字の幅が狭まる. (旧Opera環境では文字の大きさが変化する)

テキストの描画位置の制御

textAlignプロパティとtextBaselineプロパティとでテキストを描画する際の基準を設定することが出来る.

CanvasRenderingContext2D.textAlign
文字列の横の描画基準を指定する. start,end値についてはcanvas要素における現在のCSS属性directionがltrかrtlかで変化する.
left
文字列の左端を基準とする.
right
文字列の右端を基準とする.
start
文字列の始点を基準とする.
center
文字列の中央を基準とする.
end
文字列の終点を基準とする.
CanvasRenderingContext2D.textBaseline
文字列の縦の描画基準を指定する.
top
文字列の上端.
hanging
hanging基底線(フォントが持つグリフ整列の基準ラインの一つ)を基準とする.
middle
文字列の垂直位置の中心.
alpabetic
alpabetic基底線を基準とする.
ideographic
ideographic基底線を基準とする.
bottom
文字列の下端.

横位置の設定において, startとendを指定した場合はcanvasが属する要素の「direction」値によって変化する. 以下は「direction:ltr;」(左から右)と「direction:rtl;」(右から左)の比較を行ったものである. 日本語環境では「direction:ltr;」が基本である.

縦位置についてはブラウザ毎に微妙な差異が発生する場合がある.

テキストの描画幅の取得

canvas要素特有の機能として, テキストの描画幅を事前に算出するものがある. テキストの描画幅はフォントやグリフの形状によって変化するが, この機能を用いることでテキストの内容を矩形で囲むと言った処理や, 文字列の一部に下線を引くと言った処理を容易に実現することが出来る.

CanvasRenderingContext2D.measureText()
テキスト幅を算出するTextMetricsオブジェクトを取得する.
TextMetrics.width
テキストの描画幅

TextMetricsオブジェクトの拡張(tobe)

現状ではテキスト幅しか取得できないが, 将来的には次のようなプロパティが追加され, 使い勝手が良くなるかもしれない.

TextMetrics.actualBoundingBoxLeft
テキストの境界ボックスの左辺.
TextMetrics.actualBoundingBoxRight
テキストの境界ボックスの右辺.
TextMetrics.fontBoundingBoxAscent
フォントの描画上限位置.
TextMetrics.fontBoundingBoxDescent
フォントの描画下限位置.
TextMetrics.actualBoundingBoxAscent
テキストの境界ボックスの上辺.
TextMetrics.actualBoundingBoxDescent
テキストの境界ボックスの下辺.
TextMetrics.emHeightAscent
ベースラインから文字の上端までの長さ.
TextMetrics.emHeightDescent
ベースラインから文字の下端までの長さ.
TextMetrics.hangingBaseline
hanging基底線の位置.
TextMetrics.alphabeticBaseline
alphabetic基底線の位置.
TextMetrics.ideographicBaseline
ideographic基底線の位置.

以下はTextMetricsの中身を列挙してみたものだ. 環境によって使えるプロパティが異なる.

WEBフォントをcanvasに描画する

CSSの@font-face規則を利用するとcanvas要素に対しても文字列の描画にWEBフォントを用いることが出来る. 例えば次のコードをスタイルシートとして宣言しよう.

@font-face {
    font-family: 'Chela One';
    src: url(http://themes.googleusercontent.com/static/fonts/chelaone/v1/DHUBEAsCcSRMyWTJ6sisfj8E0i7KZn-EPnyo3HZu7kw.woff) format('woff');
}

こうすることでGoogleが提供しているWEBフォントが利用可能となる. しかしWEBフォントの準備が完了する前にこのフォントを呼び出した場合, 意図した結果が得られない. 例えば次の二つのコードでは描画結果が必ずしもWEBフォントの内容を反映しない. 一般に言われているhtml内部でWEBフォントを呼び出しつつ, window.loadイベントでのスクリプトを実行する方法でも不十分な場合がある.

この問題はWEBフォントの分析がwindowオブジェクトのloadイベント発生したタイミングでも完了しない場合に発生する. 従って単純には描画処理を(setTimeoutメソッド等により)若干遅延させることで発生頻度を抑えることができる. このように元来確実に読み込まれるとは限らないWEBフォントを確実にcanvas要素に描画するには, 少々追加のスクリプトを記述する必要がある.

素朴な方法でWEBフォントを確実に描画する

原理的にはWEBフォントの準備が完了したタイミングを検知し, そこを起点として描画処理を開始すれば良い. 従って前項で示したmeasureTextメソッドを利用して処理を自作する.

measureTextで得られるテキスト幅はフォントの種類に依存するため, この値を観察することでWEBフォントのロード状況を確認出来る. 予め代替フォントを併記したものと代替フォントのみを記述したコンテキストを用意しておき, それぞれで得られる文字列の幅を一定時間毎に比較する. WEBフォントが利用可能となると文字列の幅が変化し比較結果がfalseとして判定されるので, このタイミングを起点として描画処理を開始する.

document.fontsのloadingdoneイベントを用いる

CSS Font Loading Module Level 3をサポートしている環境であれば, よりスマートに対処することが可能だ. フォントのロード完了時にdocument.fontsオブジェクトがloadingdoneイベントを発生する. 従って, このイベントを起点に文字列を描画すれば良い.

8・座標軸変換

座標軸の変形と図形の描画

通常の座標軸は原点をcanvasの左上にとり, x軸を水平方向に, y軸を垂直方向にとる. 図形の描画はこの座標軸を基準としてカンバスに描画されるため, この座標軸を変形することで図形を変形することができる.

変換後の座標(x,y)における点が変換前の座標では(x',y')に相当すると考えた場合, 座標軸変換は次の行列で表される. 座標軸の変換は一般にこの3×3行列, つまりa〜fの6つの数値として表すことが出来る.

⎧x'⎫ ⎧a c e⎫⎧x⎫
⎪y'⎪=⎪b d f⎪⎪y⎪
⎩1 ⎭ ⎩0 0 1⎭⎩1⎭

CanvasRenderingContext2Dオブジェクトには内部にこの変換行列を保持しており, moveToメソッド等が呼ばれた際に座標変換を行っている.

以下はこの変換行列を操作するためのメソッドである.

CanvasRenderingContext2D.setTransform(a,b,c,d,e,f)
現在の変換行列を指定した内容で置き換える.
CanvasRenderingContext2D.transform (a,b,c,d,e,f)
現在の変換行列に指定した変換行列を右から掛ける.
CanvasRenderingContext2D.resetTransform()
現在の変換行列を恒等変換(初期値)に戻す.
CanvasRenderingContext2D.scale(sx,sy)
現在の変換行列に指定したスケール変換行列を右から掛ける.
CanvasRenderingContext2D.rotate(angle)
現在の変換行列に指定した回転行列を右から掛ける.
CanvasRenderingContext2D.translate(dx,dy)
現在の変換行列に指定した平行移動行列を右から掛ける.

例を示す. 何れも同じ図形を描画しているが, 座標軸が平行移動されていることから, 結果として得られたグラフィックでは描画位置がずれている事が判る.

これらのメソッドは重ねがけすることができる.

なお座標軸の変換メソッドの呼び出し順は, 行列の掛け算の順番に相当することから変更することができない.

座標軸変換をまたいだパス図形の定義

座標軸の変換処理は既存のパス図形には影響しない. この挙動を用いると, 座標軸の変換前後のパス切片を組み合わせて一つのパス図形とすることが出来る.

座標軸変換の種類

以下に変換メソッドの動作について示す.

行列による変換:transform/setTransform

transformメソッドは現在の変換行列に別の変換行列を(右から)掛け合わせる. setTransformメソッドは現在の変換行列を指定した行列で入れ替える.

拡大縮小:scale

x軸方向とy軸方向の倍率を指定する. 何れかの値が0の場合は図形の定義が無視されてしまう. setTransformメソッドで書き換えると次のようになる.

transform(sx,0,0,sy,0,0)

回転:rotate

原点を中心とした回転角を指定する. 引数はラジアンで指定する. setTransformメソッドで書き換えると次のようになる.

transform(cos(angle),sin(angle),-sin(angle),cos(angle),0,0)

平行移動:translate

水平方向の移動量と垂直方向の移動量を指定する. setTransformメソッドで書き換えると次のようになる.

transform(1,0,0,1,dx,dy)

変換行列の初期化

現在の変換行列を初期化するにはresetTransformメソッドを実行するかsetTransform(1,0,0,1,0,0)として恒等変換行列に入れ替えるようにする.

変換行列オブジェクトの利用

座標軸の変換状況を管理したい場合は SVGMatrix(DOMMatrix)オブジェクトを使おう. コンテキストオブジェクトを直接操作するのではなく, SVGMatrixオブジェクトの変換メソッドを用い, その計算結果をsetTransformメソッドでcanvasに適用する. setTransformメソッドとを組み合わせると変換状況を行列オブジェクトとして管理可能だ.

例を示す. コンテキストに直接座標変換を施したものをSVGMatrixでの演算を介するものに書き換えてみたものだ. scaleメソッドの名称, rotateメソッドの引数の単位に若干の相違があるものの置き換え可能である.

以下に大体のメソッド相関について示す.

メソッドの対応
座標変換CanvasRenderingContext2DSVGMatrix備考
拡大縮小ctx.scale(sx, sy)m.scaleNonUniform(sx, sy)
m.scale(s)
SVGMatrix.scaleはsx=sy時に置き換え可能.
回転ctx.rotate(rad)m.rotate(deg)canvasではラジアン値, SVGMatrixでは角度値
平行移動ctx.translate(dx, dy)m.translate(dx, dy)
初期化ctx.resetTransform()
ctx.setTransform(1, 0, 0, 1, 0, 0)
m.a = m.d = 1;
m.b = m.c = m.e = m.f = 0;
変換行列ctx.transform(a, b, c, d, e, f)m.multiply([matrix])
行列指定ctx.setTransform(a, b, c, d, e, f)m.a = a; m.b = b; m.c = c;
m.d = d; m.e = e; m.f = f;
逆行列-m.inverse()カーソル位置の算出に利用

なおcurrentTransformをサポートしている環境であればコンテキストオブジェクトから直接変換行列オブジェクトを取得可能だ.

CanvasRenderingContext2D.currentTransform
現在の変換行列を取得・設定する.
CanvasRenderingContext2D.mozCurrentTransform
(firefoxのみ)現在の変換行列を配列で取得する.

座標軸変換を図形の描画に応用する

以下, 座標軸変換の応用について示す.

楕円の描画

円を何れかの方向に引き伸ばすと楕円を得ることが出来る. しかし楕円の境界に線を引く際に座標軸の引き伸ばしをしたままstrokeメソッドを実行すると, 線の幅まで引き伸ばされてしまって見た目が良くない. この問題は一度座標軸を戻すことで解決する. このことからstrokeによる線の描画にも座標軸が用いられていることが判る.

厚みをもったstroke

上とは逆にstrokeによる線の幅を縦横不均衡とすることで, 線に厚みを持たせることが出来る.

座標軸を回転させることによる図形の定義

カンバスを逐次回転させ, 点の位置を少しづつずらして行くことで様々な図形を描画することができる. パス全体を一括で定義する場合, 三角関数を駆使せねばならず可読性が低下する恐れがあるが, この方法であれば非常に単純な構造となる.

螺旋

集中線

多角形・星型

グラデーション・パターンへの適用

パス図形を定義した後, 座標軸変換を施すことで塗り潰しの内容, 線の内容のみに変形を施すことが可能だ.

ズーム・パン機能の導入

座標軸変換を使うとcanvasグラフィックにズーム(拡大・縮小)とパン(平行移動)機能を追加可能だ. 下の例ではマウスによるドラッグ操作でグラフィックの描画位置を変更可能としている.

グラフィックのレスポンシブ化

静的なラスタ画像は一般に解像度が確定しているため, 描画サイズが変更されるとアンチエイリアシングと言った近似処理が発生してしまう. しかしcanvas要素では座標軸変換を用いることで, 既存の描画ロジックへの変更を最小限に留めつつ解像度毎に最適なグラフィックを描くことが可能となる. 具体的な手順を示す.

  1. グラフィックの内部サイズを決定する.
  2. canvas要素の見た目のサイズ(CSSサイズ)を取得する.
  3. ctx.scaleメソッドを使ってグラフィックの描画スケールを「見た目サイズ/内部サイズ」する.
  4. ctx.saveを実行し, 現在のスケール設定を保存する.
  5. 以降はこれまでと同様のサイズ200×200のスクリプトを実行する.
  6. canvas要素のサイズを変更しうるイベントに再描画処理を登録する.

次の例では, input[type=range]の入力内容によってcanvas要素のサイズを変更している. 文字グラフィックが滑らかに拡大/縮小される部分に注目して欲しい.

カーソル座標とcanvas座標

canvas要素をユーザーインターフェースに利用する場合, しばしばcanvasグラフィック上のカーソル座標を取得したい場合がある. この方法には色々なものがあるが, WEB標準を考慮するのであればCSSOM Viewの仕組みを用いるとよい.

座標変換が必要な場合

グラフィック描画の都合上座標変換が必要な場合は, 現在の変換行列の逆行列を求めて, canvas要素上の座標をcanvasグラフィック内部の座標に変換する必要がある. なお, 現状のcanvasAPIではtransform行列や逆行列が得られないので, SVGDOMの仕組みを利用する.

9・コンテキスト状態の一時保存と復元

saveメソッドとrestoreメソッド

カンバスでの描画処理はコンテキストオブジェクトを何度も使い回して行われる. この時, 図形を描画する都度その色や座標系, クリップ領域を再定義するといったことでは非常に面倒である. このような問題を解決するため, コンテキストオブジェクトには現在の各種設定を一時的に保存するsaveメソッドと元に戻すrestoreメソッドが提供されている. この機能を用いることで一般に難しいクリップ領域の削除や, 座標軸変換状況の復元などの操作が実現可能となる.

CanvasRenderingContext2D.save()
現在のコンテキストの内容をスタックに保存する. 保存されるプロパティ値は次の通り.
座標軸の変形状況, クリップ領域, strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline
CanvasRenderingContext2D.restore()
前回の状態をスタックから取り出す.

この状態の保存機能はスタックとして実装されているため, 一度restoreメソッドを実行してしまうと先ほどsaveメソッドで実行した内容はなくなってしまう. 現在の状況をまた後で用いたい場合は再度saveメソッドを実行する必要がある. また, 際限なくsaveメソッドを実行した場合スタックが溢れてしまう可能性があるので, saveメソッドとrestoreメソッドとはセットで用いるようにしたい. 特にtry/catch構文が介在している場合は, 必ずfinally句でrestoreメソッドを呼ぶようにする.

コンテキスト状態の破棄

コンテキスト状態を破棄するAPIは存在しないが, 次のようにすると現在の画像を維持しつつコンテキスト設定のみをリセットすることが出来る.

  1. getImageDataメソッドで現在の画像データを取得する.
  2. ctx.beginPath();ctx.closePath();を実行する.
    現在のパス図形をクリアする. (下の操作だけでは図形が残るものがある.)
  3. canvas.width = canva.widthを実行する.
    コンテキスト状態及びグラフィックが破棄される.
  4. (1)で保存しておいた画像データをputImageDataメソッドで書き戻す.

コンテキスト状態の列挙

コンテキスト状態の全体を取得するAPIは存在しないがfor-in構文を使って列挙することができる. 上手く設計すればコンテキストの状態を外部で管理することも可能だ. 但しFunctionやObjectと言ったものも含まれてしまうので, 必要となるものを適宜取捨選択する必要がある.

10・画像の挿入

drawImageによる画像の挿入

カンバスには図形を描画するのみならず, 既存のラスタ(ベクタ)画像ファイルを描画することが可能である.

CanvasRenderingContext2D.drawImage()
画像を描画する. HTMLImageElement/HTMLVideoElement/HTMLCanvasElement等が描画対象. なお, GIFアニメーションや動画については, 特定のフレームがスナップショット的に描画される.

メソッドの引数のパターンには次の3つが存在する.

引数imageにはHTMLImageElement(Imageオブジェクト), HTMLCanvasElement, HTMLVideoElementの何れかを指定する. その他の引数の詳しい意味合いは下記の図を参照して欲しい.

下はimg要素で読み込んだ画像ファイルである. これをカンバス要素に描画してみよう.

座標指定

指定した座標を起点に画像が描画される. canvasサイズからはみ出た部分は無視される.

矩形指定

矩形に従って画像が引き伸ばされる.

トリミング指定

画像を指定した矩形範囲で切り取り, その内容をカンバスに描画する.

座標軸変換による画像の変形

drawImageメソッドでは予め座標軸を変形しておくことで画像を変形することが可能だ. 下の例は画像を左右反転させた例である.

canvas要素の参照

drawImageメソッドでは画像の参照先として別のcanvas要素が使える. 例えばアニメーション処理を行う際に前景と背景とを別々に描画し, 最後に一つにまとめるといった構成を採ることもできる.

また自分自身を書き込むことも出来る.

画像の大きさを取得する

img要素における画像サイズにおいてもcanvas要素と同じく画像そのものサイズと画像の描画サイズの2つが定義されている.

HTMLImageElement.width/height
画像の見た目のサイズを取得する. CSSによるサイズが指定されていない場合, スクリーンに描画されていない場合は画像そのものサイズを返す.
HTMLImageElement.naturalWidth/naturalHeight
画像そのもののサイズを取得する.

例えば画像の加工を目的としているのであればnaturalWidth/naturalHeightを用いるとよい.

画像を動的に読み込む場合の注意点

外部画像を読み込む場合, html文書に定義されているimg要素を参照する動的にHTMLImageElementを生成して参照するの2つの方法が存在するが, この時注意すべき点がある. img要素による外部の画像ファイルの読込は非同期で行われるため, drawImageメソッドを実行したタイミングによってはまだ画像のロードが終わっていないケースが発生する. 例として描画に失敗するケースを示す.

この例では画像の初回読み込み時にカンバスが表示されないケースが発生する. スクリプト内部でHTMLImageElementを生成して画像を読みこませようとしているが, drawImageメソッドを実行した時点ではまだ画像の読込が完了していないので処理が失敗する. なおページをリロードすると正常に動作してしまう. これは先ほどと異なり画像ファイルがブラウザのキャッシュから読み込まれているためで, このままではあまり良い動作とは言えない.

これを回避するにはimg要素の持つonloadイベントでカンバスの描画処理を行うようにするか, img要素のcompleteメソッド使って画像読み込みの完了を確認するようにする. 画像の読込が確実に完了してからdrawImageメソッドを実行するのだ.

※なおgetElementByIdによる画像の取得が上手く行く理由は, スクリプトの開始がwindow.onloadイベントによって行われているためである. window.onloadイベントはimg要素の読込が全て完了してから発生するため, 画像の準備ができている状態でdrawImageが実行されているのだ.

複数の画像ソースを利用する場合

canvas要素に読み込む画像が複数にわたる場合はもう少し細工を施す必要がある. 読み込まれた画像の数を数え全ての画像が読み込まれたことを確認してから描画処理を開始するようにする. HTMLImageElement.completeプロパティは画像を読み込んでいない際にもtrueを返すためこの用途においては適切ではない.

画像の描画品質

画像をカンバスに描画する際の品質を設定することができる. 主にドット絵等の低解像度の画像データを拡大した際に発生するアンチエイリアスの有無・品質を指定するために用いる.

CanvasRenderingContext2D.imageSmoothingEnabled
画像を描画する際の品質を指定する. falseでアンチエイリアスを無効とする. (図形描画等には影響しない)
CanvasRenderingContext2D.imageSmoothingQuality
アンチエイリアスの品質を指定する. (low/medium/high)

画像のモザイク化

ソース画像をモザイク化するには様々な方法があるが, imageSmoothingEnabledが使えるなら一旦小さなcanvas要素に描画した内容を引き伸ばすだけで実現できる. この方法ならピクセル操作を行う場合に注意すべきクロスオリジンでの制約を考えずに済む.

補足)CSSを用いた画像のモザイク拡大

なお, CSSを使って縮小した画像を拡大する方法もある. canvas要素側の処理が単純になる反面, 指定するスタイル値がブラウザ毎にバラバラという難点がある. (これはimage-renderingプロパティの内容が二転三転しているため.) またInternet Explorer9以降では-ms-interpolation-modeプロパティが効かない.

//画像リサイズ時のアンチエイリアスを切るスタイル
image-rendering:optimizeSpeed;             /* Legal fallback */
image-rendering:-moz-crisp-edges;          /* Firefox        */
image-rendering:-o-crisp-edges;            /* Opera          */
image-rendering:-webkit-optimize-contrast; /* Safari         */
image-rendering:optimize-contrast;         /* CSS3 Proposed  */
image-rendering:crisp-edges;               /* CSS4 Proposed  */
image-rendering:pixelated;                 /* CSS4 Proposed  */
-ms-interpolation-mode:nearest-neighbor;   /* IE8+           */

出典:Showing how to zoom up a bitmap with crisp edges using HTML Canvas or CSS.

画像読み込みの応用

ここまでは単純なURLを用いた画像読み込みを行ったが, これを応用すると次のような場面においても画像を取得可能だ.

Ajax機構で取得したバイナリ画像データをcanvas要素に書き出す

XMLHttpRequestオブジェクトを使い, 画像データをバイナリデータ(Blobオブジェクト)として取得する方法. 得られたBlobオブジェクトはURL.createObjectURLメソッドでBlobデータスキームに変換することで, img要素に渡すことが可能.

一旦画像ファイルをBlobとして扱う方法は, GIFアニメーションをフレーム画像に分割すると言った場合に用いられる.

ローカル環境の画像ファイルをcanvas要素に書き出す

input[type=file]要素で画像ファイルを取得する. 得られたFileオブジェクトはBlobオブジェクトでもあるため, 画像ファイルが得られた後は先ほどと同じ.

canvas要素にドロップした画像を描画する

dromイベントからファイルオブジェクトを取得する. 画像ファイルが得られた後は先ほどと同じ.

いずれも何らかの手段で画像データに相当するBlob(File)を得るURL.createObjectURLメソッドを用いてBlobをimg要素に表示するimg要素には予めonloadイベントにcanvas要素への描画処理を記述しておく描画処理が完了したらURL.revokeObjectURLメソッドでBlobオブジェクトを開放すると言った手順をとっている点に着目しよう.

クリップボードを経由した画像の描画

出典:How can I let user paste image data from the clipboard into a canvas element in Firefox in pure Javascript?

WEBページ上の画像を右クリックした際に表示される「画像のコピー」を使ってコピーした画像を, ペースト操作(キーボード操作を含む)でcanvas要素に画像を描画する事が出来る. 実現方法には概ねpasteイベントを用いるcontenteditable属性を用いるの2つがある.

コピー用の画像(右クリックでコピーして以下のコードでペーストしてみましょう)

pasteイベントを用いるもの

pasteイベントを使って, クリップボードの中身を参照する方法. 但しこの方法は仕様が確定していないので, 動作する環境はChromeに限られる. なお, canvas要素そのものはpasteイベントを発生させないので, canvas要素を囲む何らかのHTML要素を用意する必要がある. また, そのままではペースト可能な範囲が不明瞭なので何らかの誘導が必要であろう.

contenteditable属性を用いるもの

contenteditable属性をtrueとした要素の特性をcanvas要素に応用したもの. FireFox/Chrome/InternetExplorerといった広範な環境で動作するので, 通常はこちらを選択すると良い. 以下に動作原理を示す.

例を示す. canvas要素の上に画像のペーストを検知するためのdiv要素を被せ, MutationObserverでDOMの変更を監視している.

試してみた処, canvas要素にcontenteditable属性を設定することで上記のdiv要素を省く事も出来るようだが, 安定性の面で不安が残る.

補足)テキストデータの貼り付け

上記はテキストの貼り付けに応用することができる. この場合, pasteListener内部のテキストを描画するようにする.

video要素をcanvas要素に描画する

drawImageメソッドの引数にはHTMLImageElement/HTMLCanvasElementオブジェクトの他, HTMLVideoElementオブジェクトを渡すこともできる.

getUserMediaを用いてWebカメラの映像をcanvas要素に描画する

出典:HTML5 での映像と音声の取得

WebRTCをサポートする環境ではWebカメラの映像をvideo要素に表示できる. 従ってこのvideo要素をcanvas要素に描画することでカメラ画像をcanvas要素上で加工することが可能だ.

ImageBitmapによる画像インターフェースの統一

drawImageメソッドに指定する画像としては通常HTMLImageElement/HTMLCanvasElementを用いるが, ImageBitmapオブジェクトをサポートする環境では画像に類するデータ(例えばImageDataやBlob/File等)をImageBitmapオブジェクトに変換することで統一的にdrawImageメソッドに渡すことができる. また, DOMから切り離されたオブジェクトであるため, Worker内部でも画像データを扱えるというメリットもある. なおImageBitmapオブジェクトは種類の異なるコンテキストオブジェクト間での画像データの授受を目的としていて, 今後バックグラウンドでの画像描画やWebGLにおけるテクスチャ画像の取り扱いと言った場面での活用が期待されている.

ImageBitmapFactory.createImageBitmap(source, sx, sy, sw, sh, option)
ImageBitmapオブジェクトを生成し, それを利用するためのプロミスオブジェクトを返す. sourceには画像オブジェクトを, sx,sy,sw,shにはその切り出したい範囲を指定する.
sourceとしてはHTMLImageElement, HTMLCanvasElement, HTMLVideoElement, Blob, ImageData, CanvasRenderingContext2D, ImageBitmap, SVGImageElementと言った広範な画像インターフェースを指定可能.
※Window/WorkerオブジェクトはImageBitmapFactoryインターフェースを実装しています.
optionには現状下記値の連想配列(ImageBitmapOptions)を指定可能.
imageOrientation
画像の方向(none/flipY…上下反転)
premultiplyAlpha
プレマルチプライ(アルファチャンネルの事前適用の有無)設定の指定(default/premultiply/none)
colorSpaceConversion
色空間の変更(none/default)
resizeWidth
リサイズ幅
resizeHeight
リサイズ高
resizeQuality
リサイズ品質(pixelated/low/medium/high)
※現在imageOrientationのサポートは一部のみ.
ImageBitmap
WEB環境におけるビットマップ(ラスタ)画像を表すオブジェクト. CanvasRenderingContxt2D, WebGLRenderingContext, ImageBitmapRenderingContext共通で利用可能.

createImageBitmapメソッドは直接ImageBitmapオブジェクトを返すわけではなく, それを利用するためのPromiseオブジェクトを返す. つまり, 得られたPromiseオブジェクトのthenメソッドの引数にimageBitmapが得られた際の処理を記述していく.

promise.then(onfulfilled, onrejected)
処理成功時の処理, 失敗時の処理を指定する.
promise.catch(onrejected)
処理失敗時の処理を指定する.
Promise.all([promise, …])
PromiseオブジェクトをひとくくりとしたPromiseオブジェクトを返す.

なおcloseメソッドを指定することで画像データを明示的に破棄することができる.

ImageBitmap.close()
現在保持している画像データを開放する.
ImageBitmap.width
現在保持している画像の幅
ImageBitmap.height
現在保持している画像の高さ

補足)fetchAPIと組み合わせる

Promiseという仕組みを利用するのは一見面倒だが, 現在検討中のfetchAPIと組み合わせると次のように画像データの取得処理が順序良く簡潔に記述できる.

canvas要素のスナップショットを撮る

ImageBitmapオブジェクトはcanvas要素の一時的なスナップショットを撮る目的でも利用できる. ImageDataオブジェクトによる書き戻しと異なり, filter等の画像効果を加えることができる.

補足)BitmapRenderingContextによる描画

ImageBitmapが利用可能な環境では通常利用するコンテキストオブジェクトに加え, BitmapRenderingContextというAPIが定義される. 本オブジェクトはImageBitmapオブジェクトの表示に特化しており, 唯一transferFromImageBitmapメソッドのみが提供される.

BitmapRenderingContext.transferFromImageBitmap(bmp)
ImageBitmapの内容をcanvas要素に出力する. nullを渡すと画像がクリアされる.
※現在transferImageBitmapとの表記揺れあり
BitmapRenderingContext.canvas
このコンテキストオブジェクトを生成したHTMLCanvasElement

EXIF方向値を加味した画像の描画

JPEG画像にEXIFによる「画像の方向」が設定されている場合, WEBブラウザではこの内容を無視する. 従ってこの方向を加味した画像の描画を行う場合, 一旦JPEG画像をAjaxによりバイナリデータとして取得し画像の方向データを抽出する(難易度が高い)か, Exif.jsと言ったサポートライブラリを用いることなる.

EXIF値と画像データの状態
EXIF値見た目の回転反転画像イメージ EXIF値見た目の回転反転画像イメージ
1なし実像
x,yを交換
(鏡像)
5左回転鏡像
2鏡像
x,yを交換
(鏡像)
6実像
(鏡像の鏡像)
3180度実像
x,yを交換
(鏡像)
7右回転鏡像
4鏡像
x,yを交換
(鏡像)
8実像
(鏡像の鏡像)

EXIF方向値が得られたら, その内容を元にcanvasのサイズを決定し座標軸を変換する. 下記にサンプルコード(実際の動作例はこちら)を示す.

window.onload = function(){
	var canvas;
	var ctx = canvas.getContext("2d");
	EXIF.getData(img, function(){
		var orient = img.exifdata.Orientation;
		//サイズ設定
		if(orient <= 4){
			canvas.width = img.naturalWidth;
			canvas.height = img.naturalHeight;
		}else{
			canvas.width = img.naturalHeight;
			canvas.height = img.naturalWidth;
		}
		//origin
		ctx.translate(canvas.width/2, canvas.height/2);
		//rotate
		if((orient-1)%4 >= 2){
			ctx.rotate(Math.PI);
		}
		//flip
		if(orient%2 == 0){
			ctx.scale(-1, 1);
		}
		//origin
		ctx.translate(-canvas.width/2, -canvas.height/2);
		//switch x and y
		if(orient >= 5){
			ctx.transform(0,1,1,0,0,0);
		}
		ctx.drawImage(img, 0, 0);
	});
}

11・画像の出力

データURIスキーム形式での画像出力

canvas要素に描画した内容はtoDataURLメソッドを用いることでデータURIスキーム形式の文字列で抽出することが出来る. これはcanvas要素で描いたグラフィックを再利用する上で重要な役割を果たす.

HTMLCanvasElement.toDataURL(type[, param])
データスURIキーム形式で画像を取得する.
image/png
PNG形式で取得する.
image/jpeg
JPEG形式で取得する. ※第2引数にJPEG品質値(0〜1)を設定可能.
image/webp
WEBP形式で取得する. Chrome等で有効. ※第2引数にWEBP品質値(0〜1)を設定可能.

以下の例ではカンバスに描いた内容をtoDataURLで文字列に変換している. toDataURLメソッドの引数には, 画像の形式をMIME-typeとして指定する. 通常は「image.png」もしくは「image/jpeg」の2種類が利用可能だが, 環境によってはこれ以外の形式を指定することも可能だ.

image/png…PNG形式

PNG形式は画像品質が維持されるため, グラフや図等の出力, 及びグラフィックを後々に再加工する際の一時保管に向く. その一方でデータサイズが大きくなりがちである.

生成した文字列:

image/jpeg…JPEG形式

JPEG形式は非可逆圧縮を施すことで大幅に画像データを縮小するが, その過程で画像品質が失われる. そのため, 品質劣化が目立たない自然画や最終的な画像出力に向く. また, 不透明度を表すアルファチャンネルに対応していないため, 背景色が黒色として画像が生成される.

生成した文字列:

image/webp…WEBP形式

Chrome等のBlink系のブラウザでは, WEBP形式での出力をサポートする. WEBP形式をサポートしない環境ではPNG形式として出力される.

生成した文字列:

データURIスキーム形式の画像データ

この「data:image/xxx;base64,」から始まる文字列がデータURIスキーム形式で表現された画像ファイルであり, 後続の文字の羅列はbase64形式に変換された画像のバイナリデータである. RFC 2397「The "data" URL scheme」で定められている.

[書式] data:[MIME type][;[encoding=文字コード]][;[base64]],[base64形式のバイナリデータ]

この内容はimg要素のsrc属性や, CSSのbackground-imageプロパティに設定されてHTMLファイルの中に画像を埋め込む用途で用いられる他, a要素のhref属性に設定してユーザーに画像ファイルを保存させることもできる.
リンクのサンプル
画像埋め込みのサンプル

toBlobメソッドによる画像ファイルの取得

BlobはHTML5から導入されたオブジェクトで, バッファデータとコンテンツタイプをひとまとめにしたものだ. Fileオブジェクトの基底オブジェクトでもあり, メモリ内部に展開されたファイルのようなものとして考えて良く, 様々な場面で利用される. HTMLCanvasElementではtoBlobメソッドを用いることでcanvas要素に描画した内容を直接画像ファイル化することができる.

HTMLCanvasElement.toBlob(callback, type, encoderOptions)
画像データに対応するBlobオブジェクトを生成し, 処理を行う. callbackにはBlob生成後の処理を, type,encoderOptionsにはtoDataURLメソッドと同じく画像形式とそのパラメータを指定する.
HTMLCanvasElement.msToBlob()
Internet Explorer専用. 画像データに対応するBlobオブジェクトを直接生成する. 得られる画像データはimage/png形式固定. 上記toBlobとは使い方が異なるため注意が必要.

BlobをURL文字列に変換する

Blobそのものを編集することはできないものの, URL.createObjectURLメソッドを用いることでリンクやimg要素のsrc属性に指定可能なURL文字列に変換可能だ.

URL.createObjectURL(blob)
Blobオブジェクトを直接参照するURL(「blob:」から始まるBlobURIスキーム)を生成する.
URL.revokeObjectURL(bloburl)
URL.createObjectURLメソッドで生成したBlobURLを廃棄し, Blobオブジェクトへの参照を開放する.

Blobを用いた画像データの送信

toBlobメソッドで得られたBlobをそのままFormDataに設定して送信することが出来る. 一例を示す.

toDataURLメソッドとtoBlobメソッドの使い分け

toDataURLメソッドで取得した画像データを(別ウィンドウで表示したり, リンク等に挿入するなど)ブラウザで直接利用する場合, 画像の大きさにもよるがbase64形式のデータをデコードする際にブラウザに大きな負荷がかかる. この場合は直接画像データを参照可能なBlob+URL.createObjectURLを用いたほうが良い. 一方, 生成した画像を別途生成したSVGやHTML,CSSに埋め込む場合は, toDataURLを使って画像データを埋め込むしかない.

参考)Blobを直接生成した例

toBlobメソッドをサポートしていない環境では次のようにしてBlobを生成することが可能だ.

Blob(array, options)
Blobオブジェクトを生成するコンストラクタ. arrayにBlob化したいデータを配列として指定する. optionにはBlob化する際のパラメータを指定する. ここではtype値で画像形式を指定する.
  1. canvasのtoDataURLメソッドを実行し, データURIスキーム形式のPNGデータを取得する.
  2. 上で得られた文字列からヘッダーとなる「data:image/png;base64,」を除去する.
  3. 上で得られたデータ部文字列をatobメソッドによりバイトデータ文字列に変換する.
  4. Uint8Arrayオブジェクトを生成する. 長さは上出えたバイトデータ文字列長とする.
  5. Blobコンストラクタを呼び出し, 「image/png」形式のBlobオブジェクトを生成する.
  6. URLオブジェクトのcreateObjectURLメソッドにより, 上のBlobをURL文字列に変換する.
  7. このURL文字列を元にウィンドウを開く.

補足)外部ライブラリによるPNG形式の最適化

canvas要素によるPNG画像の出力では通常24bitカラー+8bitアルファチャンネルが用いられる. これは必ずしもグラフィック内容に対して最適化されたものとはならない. 出力する画像データを最適化し, よりコンパクト化する必要がある場合は次のJavaScriptライブラリを用いると良い.

なお開発者のブロク解説による通りそのまま利用するには非常に重いライブラリであるため, 後述するWeb Workersと組み合わせて利用すると良い. 例を示す.

//pnggen.jsの内容 importScripts("../png2svgjpg/canvastool.pngencoder.min.js"); //Worker内部ではElementが存在しないのでcanvastool.pngencoder.min.jsがエラーを発する. そのためのダミー. function Element(){}; self.addEventListener("message", function(e){ var md = e.data; var encoder = new CanvasTool.PngEncoder( md.data , { bitDepth:8, colourType: CanvasTool.PngEncoder.ColourType.TRUECOLOR, width: md.w, height: md.h } ); postMessage(encoder.convert()); self.close(); }, false);

PngEncoderコンストラクタに渡すパラメータについてはソースコード等を参照してください.

canvasグラフィックの共有

単一のWEBページにおいて, 複数箇所に同一のグラフィックを描く場合, 複数のcanvas要素に同じグラフィックを描くのではなく, 単一canvas要素で描いた内容をBlobデータとしてimg要素に設定すると良い. 描画内容を共有する分, メモリ消費を抑える効果が得られる. 描画内容を複写してもよいが, canvas要素は個々に別のインスタンスとして管理されるため無駄が多い.

但しこれは静的なグラフィックに限られる. アニメーション等を含んだcanvasグラフィックの場合は, CSS背景にcanvas要素を直接設定する方法, もしくはCanvasCaptureMediaStreamを介してvideo要素を用いる方法が考えられる. が, いずれにせよ動作環境が限られているので現状ベストな対処策は存在しない.

GIFアニメーションの動作を制御する

GIF(APNG)アニメーションファイルをメモリ内部でcanvas要素に描画すると, 最初の1フレームが出力される. この動作を応用してimg要素で表示しているGIF(APNG)アニメーションの停止・開始を表現することが出来る. 操作する毎にsrc属性の参照先をgifファイルとcanvas画像ファイルとに切り替える.

12・ピクセルの操作

ピクセル情報の取得

canvas要素の内容はImageDataオブジェクトを用いることでピクセル単位で操作することが出来る. これまでのメソッドは仕様の解釈の違いから描画結果においてブラウザごとに微妙な差異が発生してしまうが, ImageDataオブジェクトを介した操作はピクセルの色を直接操作することとなるので, 原理的には全く同じ描画結果を得られる. また, 画像データをバイト配列として取得できることから, 様々な画像処理アルゴリズムやグラフィックライブラリをブラウザ上で再現することが可能だ.

ImageDataオブジェクトに関わるメソッドとしては次の3つが提供されている.

CanvasRenderingContext2D.createImageData(width,height)
指定したサイズのImageDataオブジェクトを生成する. もしくは元のImageDataと同じサイズのImageDataオブジェクトを生成する. なお, 直接生成することも出来る.
CanvasRenderingContext2D.getImageData(x,y,width,height)
canvas要素の指定した矩形領域に相当するImageDataを取得する.
CanvasRenderingContext2D.putImageData(imagedata, x, y)
ImageDataの内容をcanvas要素に書き戻す.
ImageData.width
ImageDataの幅.
ImageData.height
ImageDataの高さ.
ImageData.data
ImageDataが内部に保持しているピクセルデータ. Uint8ClampedArrayオブジェクト. ImageDataの左上ピクセルから右上→一段下の左上ピクセル→右上ピクセル…右下ピクセルの順に並んでいる. 各ピクセルデータはrgbaの4つの値から構成されており, 全体としての長さはwidth×height×4となる. 配列の中身は0〜255の値で構成されている.
Uint8ClampedArray.length/CanvasPixelArray.length
ピクセルデータ配列のサイズを取得する.
Uint8ClampedArray.set(array, offset)
配列の内容をarrayで上書きする.offsetは上書きを開始する位置を指定する.

図で表すと次のようになる.

ImageDataオブジェクトはcanvas要素におけるピクセルの配列に相当するオブジェクトであり, ctx.createImageData(width, height)(中身は黒の透明/ゼロクリアで充填されている)ctx.createImageData(imageData)(サイズのみ引き継ぐ)ctx.getImageData(x, y, width, height)(canvas要素の描画内容のスナップショットを格納する)の何れかを実行することで取得することが出来る.

imageDataのdataプロパティに実際のピクセル情報を格納するデータ配列(Uint8ClampedArray)が設定されている. このdataプロパティの中身を操作することでピクセル毎の操作が可能となる. dataプロパティのデータの並びは図のように左上から右下までを1列に並べたものとなっており, 1ピクセルはR(赤)G(緑)B(青)A(不透明度)から構成されている. 例えば座標(2,3)の緑の値を取得する場合はimageData.data[(2+(3+1)*width)*4+1];とする.

ImageDataのdataプロパティを編集する場合は0〜255の間で値を編集する. これはcanvas要素における画素データのrgba要素が256階調(8bit)で表現されていることによる. なお0〜255の範囲外の値を設定した場合はUint8ClampedArrayオブジェクトの仕様により強制的に0もしくは255に調整される.

ImageDataの編集が完了したら, putImageDataメソッドでその内容をcanvas要素に書き戻すことが出来る. ImageDataの左上の座標をのみを指定する方法と, ImageDataにおける矩形範囲を指定して書き戻す方法がある.

下記はfillRectメソッドを使用せず矩形範囲を自力で塗りつぶした例である.

なおputImageDataメソッドによる画像の配置は, 現在のクリップ領域及び座標軸の変形による影響を受けず, canvasの画素を直接描き換える.

putImageDataに矩形範囲を指定した場合, ImageDataのその矩形範囲外のピクセルデータはcanvas要素に書き戻されない.

色のRGBA値を得る

canvas要素でグラフィックを描く場合, 色情報の形式を統一したい場合がある. 例えばHTML色「navy」に塗りつぶされているピクセルを抽出したい場合, まずnavyに対応するRGB値を求めねばならない.

このような場合は実際にその色を描画してみて, ピクセルの値を抽出すると良い. fillStyleプロパティを参照しても良いが, 文字列として返されるため使い勝手が良くない.

rgba:(hex:)

画像のセーブ・キャッシュ

canvasで描いたグラフィックを一時的に保管する場合, toDataURL/toBlobメソッドを用いてPNG画像としても良いが, imageDataとして扱ったほうが後々の読み込み処理が単純になる(toDataURL/toBlob/ImageBitmapを使った場合はいずれも非同期処理が発生する). 例を示す.

複数の履歴を必要とするのであれば, 配列を用いてstack機構を構成すると良い. push/pop/shiftメソッドで簡単に記述できる. 但し, 1履歴保持する毎にメモリが大きく消費されていく点に注意しよう.

補足)localStorageによる画像のキャッシュ

ページを閉じた後もデータをキャッシュしておき, 次回のページロード時に処理を再開する場合は, グラフィックを文字列化しlocalStrageに格納(永続化)する方法がある. この場合ではImageDataを直接文字列化すると素のバイナリデータゆえに無駄が多いので, toDataURLメソッドを用いてPNGやJPEG形式したほうが良い. しかしlocalStorageによるキャッシュはcookieによるデータキャッシュの代替であり, 画像データのような巨大なものが挿入されることを想定していない. そのため画像サイズが大きい場合はそれに応じてキャッシュファイルも肥大化することとなり, 同期的なファイル読み込みに伴うページロード時のパフォーマンス低下が発生する.

localStorage.setItem(key, value)
キーに対する値(文字列)を格納する.
localStorage.getItem(key)
キーに対する値(文字列)を取得する.
localStorage.removeItem(key)
キーに対する値を削除する.

フィルタの実装

filterプロパティに設定可能なフィルタ関数は限られているが, ImageDataが存在するお陰で必要とするフィルタ機能を自力で実装することが出来る. 簡単な例を示す.

RGB交換

ピクセル毎のRGB(A)値を交換し, canvasに書き戻す.

グレイスケール

ピクセル毎にR,G,Bの重み付き平均を取ることでグレイスケール化することが出来る.

セピア化

R, G, B毎の重みを変更するとセピア化の効果を得ることが出来る.

この他にもぼかしや, 輪郭抽出などのコンボリューション行列フィルタの実装も可能である. 詳しくはこちらを参照のこと. なお, 画素毎に大量の計算処理を行うこととなるため, 画像サイズが大きくなるにつれブラウザの動作パフォーマンスに影響を及ぼす. 従って下記に示すasm.jsを使った高速化やworkerを使った処理のバックグラウンド化と組み合わせることを検討したい.

補足)ピクセル操作にasm.jsを適用する

一般にピクセル操作は型付き配列に対する数値演算の塊と言える. 従って, asm.jsを適用することで処理速度の向上が見込まれる. 簡単な例を示す.

ImageDataの直接生成とメモリの再利用

ImageDataオブジェクトはcreateImageDataメソッドを用いずとも直接生成することも可能だ. ImageDataオブジェクトを何度も使い捨てにする用途であれば, 予め多めのメモリを確保しておくことで単一のメモリ領域を使いまわすことが出来る.

  1. 十分なサイズのメモリ領域(ArrayBuffer)を確保する.
  2. Uint8ClampedArray(ArrayBufferView)を生成する.
  3. ImageDataを生成する.
  4. ImageData(もしくはUint8ClampldArray)の内容を編集しcanvasに書き戻す.

この方法を使えばAjax機構で得た画像の生データ(ArrayBuffer)を直接canvasに読み込ませることもできる.

13・オフスクリーンレンダリング

オフスクリーンレンダリングとは

一般にスクリーンに表示されているcanvas要素に直接描画を行うことはコストが高い操作である. なぜなら, apiを呼び出す毎にスクリーンが描き変わってしまうからだ. この特性は単一のグラフィックのサイズが小さいうち, 書き換え頻度が少ないうちはそれほど問題とはならないが, アニメーションや複雑な描画処理を必要とする場合に深刻なパフォーマンス劣化をもたらす. そこで一旦グラフィックをスクリーン描画処理の影響を受けない場所で完成させた上で, スクリーンに書き戻す方法が考えられる. これをオフスクリーンレンダリングと呼ぶ.

canvas要素を用いる上でオフスクリーンレンダリングを実現する場合, 別途HTMLCanvasElementを直接用いる方法と, 専用のカンバスオブジェクトを用いる方法の2つが挙げられる. なお, 後者は現在APIが策定中であり一部の環境でのみ動作可能である.

基本のオフスクリーンレンダリング

canvas要素におけるオフスクリーンレンダリングを行うには次のようにする.

  1. スクリーン表示用のcanvas要素(A)を取得する
  2. グラフィック描画用のcanvas要素(B)を生成する
    これはスクリプト変数に格納するだけでdomに追加する必要はない.
  3. (B)にグラフィックを描画していく
  4. (B)が描きあがったらその内容を(A)に転写する
  5. ※繰り返す場合は3〜4を繰り返す.

OffscreenCanvasの利用

このオフスクリーンレンダリングに特化したAPIとしてOffscreenCanvasオブジェクトが提案されている. グラフィックの描画処理をOffscreenCanvasオブジェクトで行い, その結果をcanvas要素に書き戻すようにする. そのため, HTMLCanvasElementオブジェクトにはtransferControlToOffscreenメソッドが追加されている. 本メソッドを実行するとOffscreenCanvasオブジェクトが制せされ, このオフスクリーンカンバスへの描画を簡単に元のcanvas要素に描画することが可能になる.

HTMLCanvasElement.transferControlToOffscreen()
OffscreenCanvasオブジェクトを生成する.
OffscreenCanvas(w, h)
直接OffscreenCanvasオブジェクトを生成する.
OffscreenCanvas.getContext(type [, arguments])
グラフィック描画のためのコンテキストオブジェクトを取得する.
内容はHTMLCanvasElement.getContextに同じ.
OffscreenCanvas.width
カンバス画像の幅
OffscreenCanvas.height
カンバス画像の高さ
OffscreenCanvas.toBlob(type, option)
画像データを処理するためのPromiseオブジェクトを取得する.
OffscreenCanvas.transferToImageBitmap()
画像データをImageBitmapオブジェクトとして取得する.

OffscreenCanvasオブジェクトでの描画結果を複数のcanvas要素に描画する場合は, ImageBitmapオブジェクトを介してグラフィックを転写する.

Web Workersを用いたバックグラウンド処理化

以上はブラウザのメインスレッドを用いたグラフィックの描画であったが, これをより効果的に行うためにはグラフィック描画処理を別プロセスとして行わせたい.

そこでWorkerオブジェクトを生成するとメインとなるブラウザ処理とは別のプロセスが起動し, postMessageメソッドを介して非同期に処理をさせることが可能となる. なおWorker内部では利用可能なオブジェクトに制限があるため, 専らデータ分析や他言語からの画像処理アルゴリズムの移植と言ったケースに利用される.

ブラウザ(Worker呼び出し)側

一般的なオブジェクトと同様にWorkerインスタンスを生成し, postMessageメソッドで処理を引き渡す.

Worker(url)
Workerを生成する. 引数としてバックグラウンド処理化したいスクリプトのURLを指定する.
Worker.onmessage = function(e){}
バックグラウンド処理からのメッセージを受信した際のコールバック関数を指定する. addEventListenerメソッドを使っても良い.
引数eはMessageEvent. dataプロパティにWorker内部で呼び出されたpostMessageの引数が設定されている.
Worker.onerror = function(e){}
バックグラウンド処理においてエラーが発生した際のコールバック関数を指定する. addEventListenerメソッドを使っても良い.
引数eはErrorEvent(message/filename/lineno)
Worker.postMessage(message[,[transfer]])
バックグラウンド処理にメッセージを送信する. 引数には処理に渡したいデータをオブジェクト(連想配列)にまとめて指定することが可能. これはつまり, どのようなメッセージをWorkerに送るべきかについての設計が必要ということでもある.
ポストしたメッセージデータは通常バックグラウンド処理に渡す際にコピーされるが, ArrayBufferオブジェクト(Transferableなオブジェクト)であれば第2引数にリストとして記述しておくとバックグラウンド処理に転送(アクセス権が移譲)されデータコピーに伴う負荷を回避できる. なお, 転送されたオブジェクトは元のスレッドからはアクセスできなくなる.
Worker.terminate()
バックグラウンド処理を強制終了する.

Worker内部

変数selfはグローバルスコープを参照するので一般にwindowと同じものを指すが, Worker内部ではWorkerGlobalScopeを指す. そのため, 特定のオブジェクト(基本データ型, 配列, Uint8Array等のTypedArray, setTimeout/setInterval, XMLHttpRequest等)以外は利用できない.

WorkerGlobalScope.postMessage(data[,[transfer]])
ブラウザ処理側にメッセージを送信する. 引数には処理結果として返したいデータをオブジェクトにまとめて指定する. なお第2引数に指定したArrayBufferオブジェクト(Transferableなオブジェクト)はメイン処理に転送される.
WorkerGlobalScope.onmessage = function(e){}
ブラウザ処理側からのメッセージを受信した際のコールバック関数を指定する. addEventListenerメソッドを使っても良い.
引数eはMessageEvent. dataプロパティにブラウザ側で呼び出されたpostMessageの引数が設定されている.
WorkerGlobalScope.close()
バックグラウンド処理を終了する. これ以上Workerによる処理が必要なければ, 明示的に呼び出してプロセスを開放する必要がある.
WorkerGlobalScope.importScripts(url[, …])
バックグラウンド処理内で使いたいJavaScriptライブラリを読み込む.

図にすると次のようになる. スクリーン描画を行うメインスレッドとWorkerスレッドとはmessageイベントを介して非同期に通信を行うため, 一般的なメソッド呼び出しのように同期的に処理結果が得られるわけではない.

Web Workersを利用した例を示す. データの授受にgetImageDataメソッドで取得したUint8ClampedArrayを利用するが, 内部のArrayBufferがTransferableなのでこれをpostMessageメソッドの第2引数に指定している.

//worker.jsの内容 self.addEventListener("message", function(e){ var data = e.data; for(var i = 0, len = data.length; i<len; i++){ data[i] = Math.round(Math.random()*255); } postMessage(data, [data.buffer]); //これ以上Workerを活かしておく必要がないので閉じてしまう. self.close(); }, false);

OffscreenCanvasを使って描画処理をWorkerに委譲する

OffscreenCanvasオブジェクトはTransferableインターフェースを実装している. そのため, transferControlToOffscreenで生成したOffscreenCanvasをWorkerコンテキスト側に転送してcanvas要素の描画を行わせることが可能である.

//worker内部のコード "use strict"; (function(){ self.addEventListener("message", function(e){ //受け取ったcanvasにグラフィックを描画していく var offscreen = e.data.canvas; var ctx = offscreen.getContext("webgl"); ctx.viewport(0, 0, 200, 200); ctx.enable(ctx.SCISSOR_TEST); ctx.scissor(50, 50, 100, 100); ctx.clearColor(0, 0, 1, 1); ctx.clear(ctx.COLOR_BUFFER_BIT); ctx.commit();//元のcanvas要素に描画結果を転送する self.close(); }, false); })();

14・canvas要素におけるセキュリティ

origin-cleanフラグによるセキュリティ制御

canvas要素はグラフィックを描画する上で様々な機能を提供しており非常に高機能である反面, 様々な危険性を孕んでいる. そのため, 下記に示す一部のメソッドについては特定の条件下においては実行できないように制限されている.

これらのメソッドの利用可否はcanvas要素の内部でフラグ値として管理されており, このフラグをorigin-cleanフラグと呼ぶ. canvas要素が生成された直後はこのフラグはtrueに設定されているが, 下記のような同一生成元ポリシーを侵すような操作が為されるとフラグの値がfalseに設定される.

ここで「同一生成元(same-origin)」であるとは参照しているurlのプロトコルが同一ポート番号が同一ホストが同一であることを指す. 例えばWEBページhttp://www.sample.com/main.htmlと生成元が同一と判定されるものは, 次の5つのうち上の2つのみである.

フラグの値がfalseの場合に上記メソッドを実行するとセキュリティエラー(SecurityError)が発生する. またフラグの値は連鎖するため, origin-cleanフラグがfalseのcanvas要素の内容を別のcanvas要素に描き込んだ場合, 描き込み先のcanvas要素のフラグもfalseとなる. また, 一旦falseとなったorigin-cleanフラグはAPI等から操作することはできないので, 継続してcanvas要素のフル機能を使いたいのであれば新たなHTMLCanvasElementを生成しなければならない.

例を示す.

次はorigin-cleanフラグが連鎖する例である.

この「異なるドメイン」の判定が, ブラウザによって異なる. 特に上位ディレクトリを参照する「../」を利用した画像の参照を行った場合, 同一のドメインに存在する画像であるにもかかわらずorigin-cleanフラグがfalseと判定されることがある. 例を示す.

corsによる動作の緩和

canvas要素におけるoregin-cleanの動作はより一般的なクロスドメインでのリソース参照についての仕様を規定するcors(Cross-Origin Resource Sharing)に準拠している. ブラウザ, WEBサーバー共にcorsに対応している必要はあるが, WEBサーバーがレスポンスヘッダーにAccess-Control-Allow-Origin値を適切に付加することにより, origin-cleanフラグを汚染すること無くクロスドメインでの画像参照が可能となる.

HTMLImageElement.crossorigin
corsによるリクエストの方法を指定する. レスポンスヘッダーにAccess-Control-Allow-Origin値が設定されていた場合, 元の文書と生成元が同一であるものとして扱われる.
anonymous
認証無しのリクエストを行う.
use-credentials
認証ありのリクエストを行う.
[未指定]
これまでどおりのリクエストを行う.

canvas要素における画像リソースの隠蔽

WEBを介したデータのやりとりは必然的にデータの複製を伴う. 従って, 金銭的に価値のある画像をユーザー側のブラウザに表示しつつ画像の複製を禁止することは事実上不可能と言える. しかし要件によってはこの矛盾した内容を強引に解決すべき場合もあるだろう. ここでは簡単にではあるがcanvas要素を用いてユーザーによる画像コピーを防ぐ(困難にする)方法(つまり, アプリケーションベンダーサイドのセキュリティ)について考察する.

15・アニメーションの実現と利用

基本的なアニメーションのパターン

canvas要素そのものにはアニメーションを行う機構は存在しない. 従ってJavaScriptの機能を使ってアニメーションを実現することとなる. なおここでは簡単な例を示す.

基本的な処理の流れは次の通り.

  1. 事前に描画したい画像等をロードしておく.
  2. canvas要素の内容の一部/全体をクリアする. (全体上書きが発生するなら必要なし)
  3. 図形の位置を算出する.
  4. 図形を描画する.
  5. この2〜4の処理を繰り返す.
  6. 規定回数繰り返した, もしくは規定時間が過ぎたら処理を終了する.

この繰り返し処理を実現する方法には無限ループを用いる(非推奨)setIntervalメソッドを用いる(ゲーム等)setTimeoutを用いる(古典的)requestAnimationFrameを用いる(汎用)等様々なものがあるが, 最も素朴な方法はsetTimeoutによる処理だろう.

アニメーション処理は実装の方法によって動作パフォーマンスが大きく変化する. 繰り返し処理の中でよく使われるオブジェクト, 配列, グローバル関数, 文字列リテラルなどについてはできるだけローカル変数として持たせるようにするとよい.

アニメーションタイミング制御APIの利用

アニメーションを実装するのであれば, 古典的なsetTimeout関数によるループ処理よりも専用のアニメーションタイミング制御APIを用いたほうが良い. これはスクリーンの再描画に合わせて処理を呼び出す他, フォーカスを持たないタブや背後で動作しているウインドウでの呼び出し頻度を低くすることで, アニメーションコストを最適化する効果をもたらす.

window.requestAnimationFrame(callback)
アニメーションフレームを要求する. return handle値. callback関数の引数にページ表示時からの経過時間(高精度)が渡される.
window.cancelAnimationFrame(handle)
アニメーションの実行をキャンセルする.

サンプルを示す. requestAnimationFrameメソッドを用いる場合, 単位時間あたりの描画回数が不定となるため, アニメーションの進捗を描画回数で考えるのではなく, 現在時刻から開始時刻を引いた値で計算する.

高精度タイムスタンプを利用しアニメーションの精度を高める

performance.nowをサポートする環境ではDate.nowメソッドの代わりに用いることでアニメーションの精度を上げることが出来る. Date.nowメソッドが現在時刻の整数ミリ秒表現(DOMTimeStamp)を返すのに対し, (window.)performance.nowメソッドは当該ドキュメントの表示開始時からの経過時間を浮動小数点数ミリ秒形式(DOMHighResTimeStamp)で表現したものを返す. ミリ秒以下の精密な現在時刻が得られるため, それに伴いアニメーションの精度が高まる. なお, 時刻値の基準がそれぞれ異なるため, 混在させることは出来ない.

Web Animations APIをcanvas要素に応用する

WEB環境におけるアニメーションの実現にはCSSアニメーションによるもの(主にSVGにおける)SMILアニメーションによるものスクリプトによるものの3つがあり, これらは並行して発展してきた. しかし本質的に同じものであることから, アニメーション機構を改めてWeb Animationsという単一の概念・仕様の下でそれぞれを再定義する試みが為されている. スクリプト向けには新たにWeb Animations APIが定義され, アニメーションを“作る”のではなく“制御”する統一的な手段が提供される.

Web Animations APIでは基本的に特定のノードに対するスタイルを徐々に変化させることでアニメーションを表現するが, その過程でアニメーションの進捗値が得られる. canvas要素ではこの値を元にグラフィックを描くことでアニメーションを実現する. 手順を示す.

  1. KeyframeEffectオブジェクトを生成する.
    Animationオブジェクトを生成するために, まずどのようなアニメーションとするか(アニメーションの長さ, 繰り返し回数など)をKeyframeEffectオブジェクトで定義する.
  2. Animationオブジェクトを生成する.
    コンストラクタには上記KeyframeEffectオブジェクトのほか, document.timelineを渡す. これはアニメーションをこのdocumentの管理下に置くことを示す.
  3. Animationのステータス値に応じたcanvas描画処理を制御する
    アニメーションには"idol"(停止中), "pending"(実行待ち), "running"(実行中), "paused"(一時停止中), "finished"(完了済み)の状態が定義されており, これらの内容を元にcanvasグラフィックの描画を継続する(つまりrequestAnimationFrameメソッドを実行する)かどうかを判断する.
  4. Animationの進捗値を元にcanvas描画処理を記述する
    アニメーションからはアニメーション進捗値(progress:0〜1)が得られる. この値は(1)で記述したアニメーション設定を反映しており, この内容を元にグラフィックを描くことでAnimationオブジェクトを使ったアニメーションの制御が可能となる.
  5. playメソッドを実行し, アニメーションを開始する.
    一時停止, キャンセルと言った処理をAnimationオブジェクトに任せる.

アニメーション処理の実際

単一のグラフィック生成ではそれほど気にならないものの, canvas要素によるアニメーションはその実装の仕方でパフォーマンスに雲泥の差が生まれる. 以下に注意すべき点について述べる.

オフスクリーンレンダリングを活用する

画面に表示されているcanvas要素に対する操作はスクリーンの書き換えを伴うが, 一般にこの書き換えはコストの高い処理であるためできるだけその頻度を減らしたい. そこで一旦メモリ内の(スクリプト変数に格納された)HTMLCanvasElementインスタンスに対して1フレーム分描画を行った後, DOM内のcanvas要素に複写する方法がある. これをオフスクリーンレンダリングと呼ぶ. 画面には常に出来上がったフレームのみが描画されるため, 必要最低限のスクリーン書き換えで済む. またこれを応用すると, 内部的に1秒間に60フレーム分の描画を行い, 実際の表示にrequestAnimationFrameメソッドを用いることも出来る. この場合, スクリーンの描画頻度を変更することが容易となる.

stateの変更回数を減らす

fillStyleやstrokeStyle等のコンテキストオブジェクトのプロパティを変更する操作は意外にコストが高い. 従って描画スタイルの変更がループ処理の内部に存在する場合はその処理をループの外に移動できないか検討する. また, translateメソッドによる平行移動, scaleメソッドによる拡大, rotateメソッドによる回転処理は自薦に描画メソッドに渡す座標データを変換しておくことで対応できる.

stroke/fillメソッドの実行回数を減らす

グラフィックの描画回数はできるだけ抑えたほうが良い. 複数のパス図形を逐次描画するのではなく, 一つのメソッドで描画できないか検討する.

グラフィックの再描画範囲を限定する

canvas要素にグラフィックを描く操作そのものが高コストと言える. 従ってフレーム書き換えの度にグラフィック全体を書き換えるのではなく, 必要な範囲のみを書き換えるようにする.

グラフィックを構造化(レイヤ化)する

頻繁に書き換える必要のない背景画像については別のcanvas要素に描画するようにし, 複数のcanvas要素を重ねることで目的のグラフィックを得るようにする. また, その際前面に配置するcanvasは必要最低限のサイズとしておき, css等で描画位置を指定する. 同様にCSSのbackground-imageとしてcanvas要素をレイヤ化するのも効果的である.

アンチエイリアスの発生を抑える

strokeが複数の画素にまたがる場合やdrawImageメソッドの引数に1px以下のサイズが指定された場合, 自動的にアンチエイリアシングが働く. アンチエイリアシングは本質的にコストの高い処理であるため, 処理上無視できるのであれば, 事前に少数値を調整するなどして描画対象のグラフィックがピクセル境界に収まるようにする. 同様にimageSmoosingEnabledをfalseとして画像スケール時の処理を省く.

アルファチャンネルを無効化する

描画対象のcanvasグラフィックに透過処理が必要ない場合は, 明示的にアルファチャンネルを無効化(getContextメソッドに{alpha: false}スイッチを渡す)することで若干の処理速度の向上が見込める.

アニメーション処理をイベント処理に応用する

canvas要素等のDOMオブジェクトが発する各種イベントを元にグラフィックを描く際, イベントの発生頻度を考慮しなかった場合著しいパフォーマンス低下を招く. そのため, canvasアニメーションのテクニックをイベントドリブンなグラフィック描画処理に応用することで, 必要最低限度のスクリーン書き換えで済む.

  1. 処理の開始を宣言する.
  2. requestAnimationFrameメソッドを使って定期的にグラフィックを描画する.
  3. イベント発生時にはグラフィック描画のためのパラメータのみを書き換える.
  4. 処理を終了する.

未対策のコードと対策済みのコードを示す. マウス位置にグラフィックを描く場合, 素朴にはmousemoveイベントでcanvas要素を書き換えるが, それでは書き換え頻度が過剰である. そこで, mouseenterとmouseoutイベントを, アニメーション化したグラフィック描画処理の開始/終了指示に割り当て, mousemoveイベントではグラフィック描画に必要となる座標パラメータの書き換えのみに留めている.

canvasアニメーションを動画として扱う

canvas要素で生成したアニメーションは一般にそのcanvas要素でのみ確認可能だ. だが, 現在検討されているWebRTC及びMedia Capture from DOM Elementsを用いると, canvasアニメーションを動画として扱うことが可能となり, アニメーションの再利用が可能になる. ここではブラウザ内で完結するvideo要素への転送とアニメーションの録画機能についてのみ述べる.

canvas要素の内容をvideo要素に転送する

canvas要素のcaptureStreamメソッドを実行するとCanvasCaptureMediaStreamオブジェクトが得られる. このオブジェクトをvideo要素のsrcObjectプロパティに渡すことでcanvas要素の描画状態をvideo要素に転送することが可能だ.

HTMLCanvasElement.captureStream(rate)
ストリームオブジェクト(CanvasCaptureMediaStream)を生成する. 引数にはフレームレートを指定する.
HTMLMediaElement.srcObject
再生対象のストリームオブジェクトを取得・設定する.
CanvasCaptureMediaStream
canvas要素をキャプチャするストリームオブジェクト. MediaStreamオブジェクトを継承している.
CanvasCaptureMediaStream.canvas
キャプチャ中のcanvas要素を取得する.
CanvasCaptureMediaStream.requestFrame()
実行時のcanvas要素内の描画内容を強制的にストリームに出力する. グラフィック書き換えのタイミングで実行すると効率が良いかもしれない.

ストリーミングの記録

CanvasCaptureMediaStreamオブジェクトはMeidaRecorderオブジェクトでBlob(video/webm)形式に変換可能だ. 得られたBlobオブジェクトはURLオブジェクトでurl文字列に変換することで, videoオブジェクトで録画結果を再生したり, a要素を使ってダウンロードすることができる.

MediaRecorder(stream)
ストリームオブジェクトを録画するためのレコーダを生成する.
MediaRecorder.start()
録画を開始する.
MediaRecorder.stop()
録画を終了する.
MediaRecorder.ondataavailable
録画結果のデータ出力が整った際の処理を設定する. 引数として与えられるBlobEventのdataプロパティが録画結果のBlobオブジェクトにあたる.
[保存]

アニメーション出力方法・まとめ

以下にアニメーション出力方法についてまとめよう. 新たに追加された仕組みにより, 今後下に掲げる3つの方法でcanvasアニメーションを出力することが出来る.

  1. canvas/canvas
    通常のcanvas要素でアニメーションを作成し, スクリーン上のcanvas要素に表示する方法. これまで広く使われてきた方法で, 広範な環境で動作する.
  2. video/canvas
    通常のcanvas要素でアニメーションを作成し, スクリーン上のvideo要素に表示する方法. streamを介するため動作環境が限られる.
  3. canvas/offscreenCanvas
    offscreenCanvasでアニメーションを生成し, スクリーン上のcanvas要素に表示する方法.

実際にフレームレート等を計測したわけではないが, 2のvideo/canvasの方法は1のcanvas/canvasよりも若干CPUの負荷が軽減されるようだ. また仕様が未確定なものの, 3のOffscreenCanvasを用いた方法は画像転送時に余計な処理が入らないことから, パフォーマンス面に優れることが想定される.

16・SVG(scalable vector graphics)との連携

SVGとは

SVGはXMLで定義されている二次元ベクタグラフィック記述言語であり, SVGで記述されたグラフィックはcanvas要素と同様に現在ほとんどのWEBブラウザで表示することができる. (本文書の説明図はこのSVGを用いている. )SVGを用いたグラフィックの例を示す.

svgによるグラフィック

SVG画像はJPEG形式やPNG形式のラスタ画像と同様にcanvas要素に描き込むことができる.

動的なSVGをcanvas要素に描画する流れ

SVGはテキストで表されているため, 容易に中身を書き換えることが出来る. そこで動的にSVGのソースコードを生成し, その内容をcanvas要素に描くことを考える.

SVGのソースコードの入手方法としては概ね次の2つがある.

この操作で得られたSVGソースコード文字列をデータURIスキーム(もしくはBlobURIスキーム)形式に変換しimg要素に読み込ませることでSVGグラフィックをcanvas要素に描くことが可能となる.

//SVGをcanvasに描画する流れ

//(1)SVGソースコードを入手する
//--DOMの内容からSVGを取得する場合
var svg = document.getElementById("inlinesvg").cloneNode(true);
//SVGに対する何らかの操作
var serializer = new XMLSerializer();
var svgsource = serializer.serializeToString(svg);

//--スクリプトでSVGソースを生成する場合
var svgsource = "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' "
  + "width='200px' height='200px'>"
  + "<rect x='50' y='50' width='100' height='100'/>"
  + "</svg>";//SVGソースをurl文字列に変換する

//(2)URLを生成する
//--データURIスキームを利用する場合
var url = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svgsource);

//--BlobURIスキームを利用する場合
var url = URL.createObjectURL(new Blob([svgsource], {type: "image/svg+xml;charset=utf-8"});

//(2)SVGを読み込む
var image = new Image();
image.onload = function(){
  //canvasにSVGの内容を描画するコード
};
image.src = url;

SVGの機能をcanvasに応用する

前述したようにSVGには様々な機能が備わっていて, canvas単体では実現が面倒な処理であってもSVGであれば簡単に記述することが出来る事も多い. 従ってSVGをマクロ的な用途で用いるとcanvasの表現力が格段に向上する. 代表的なテクニックについて示す.

SVGを介したHTMLの描画

SVGにはforeignObject要素というXHTMLやMathMLで定義された要素を埋め込むための仕組みが存在する. この仕組みを利用するとHTMLの内容をcanvasに描画することができる. 例として下のテーブルをcanvasに描画してみよう.

table in canvas
ヘッダヘッダヘッダ
111213
212223
313233
414243
HTMLをPNG形式に出力した結果

スクリーンに表示しているスタイルはSVG画像化する際に失われてしまうので, SVG内部のstyle要素として補っておく必要がある.

SVG Filterを用いたフィルタ処理

canvas要素で描いた内容をtoDataURLメソッドを用いて一旦SVGファイルに埋め込み, それを再度canvas要素に書き戻すことができる. この動作を応用するとSVGFilterをcanvas要素のグラフィックに直接適用することも可能である. 下ではSVGのガウスぼかしフィルタをcanvas要素の内容に適用している.

一般にグラフィックに対するフィルタ処理には専用のアルゴリズムを実装せねばならないが, この部分をSVG組み込みの処理で置き換えることが出来る. なお先ほどと同様にorigin-cleanフラグがfalseとなる環境もあるため, 何度も繰り返すといった用途では注意が必要である.

SVGDOMを利用する

上記はSVGグラフィックそのものをcanvasに描画する例だが, SVGDOMやSVGの機能そのものを直接canvasでの描画処理に応用することも可能だ.

SVGパスデータ文字列を使ったパス図形の定義

Path2Dオブジェクトを使うことでSVGパスデータ文字列を使ったグラフィックの描画が可能となる. 詳しくはPath2Dの項を参照されたい.

SVGPathElementを用いたパス計算

SVGのSVGPathElementにはHTMLCanvasElementには存在しない様々なベジェ曲線やパス図形に関わるユーティリティーメソッドが提供されている. 従ってこれらをcanvas要素でのグラフィック描画に活かすことで表現の幅を広げることができる. 下の例ではgetPointAtLengthメソッドを使ってベジェ曲線上の特定の長さの位置に印をつけている.

パスに沿ったテキストの描画

先ほどの応用で, 1文字ずつ描画位置を決定することでパスに沿ったテキストの描画が可能となる.

17・アクセシビリティ

アクセシビリティとは

アクセシビリティとはユーザーや環境を問わずに伝えたい内容(コンテンツ)を相手に伝達できるかどうかを表す尺度である. WEB環境において, 単にHTMLで記述しただけのコンテンツは「本質的に伝えたい内容」をHTML形式でマークアップしたに過ぎず, 受け手の条件(視聴覚条件, 年齢条件)・環境(スクリーン, OS, ブラウザ)によっては正しく解釈されるとは限らない. 従ってより広範な環境に正しく伝わる・動作する(アクセシビリティを確保する)ための下準備をする必要がある.

アクセシビリティとフォールバック

外部リソースを参照したりマルチメディアに関わる要素については, あらゆる環境で有効に動作するとは限らない. 例えばスクリーンリーダー環境下においては視覚的なコンテンツは意味を持たない. そのためこのような要素については, 予めそこにどのようなデータが存在するのかを記述しておくことが推奨されている. この記述をフォールバック(代替)コンテンツと呼び, 元となるコンテンツを表示・出力できない場合にその代わりとして表示される. こうすることで, (100%ではないにせよ)制作者が意図した内容が相手に伝わりやすくなる, つまりアクセシビリティが改善する.

フォールバックコンテンツの指定が可能な要素にはimg要素(alt属性にフォールバックコンテンツを設定する. ), object要素, iframe要素があるが, canvas要素においてもその子要素にフォールバックコンテンツを設定することができる. その際, フォールバックコンテンツとして配置可能な内容としては静的な代替画像, 描画した内容についての説明, 描画した文字列のほか, canvas要素だけの特徴としてa要素によるリンク, input要素等が挙げられる.

canvas要素におけるフォールバックコンテンツ表示の条件

canvas要素におけるフォールバックコンテンツが表示される条件は次の何れかを充たす場合である.

フォールバックコンテンツとの連携

一般的なフォールバックコンテンツは, それを囲む要素が有効となる環境であれば通常スクリーンに表示されない. しかしcanvas要素においては非表示であるにも関わらずフォールバックコンテンツへのフォーカスの移動やキーボードによる値の変更が可能である.

なぜこのような挙動を取るのかについては明確な理由がある. それは先ほど見た「HTML文書が提供する内容は(canvas要素の有効・無効に関わらず)本質的に同じであるべき」という原則である. つまりcanvas要素の有効無効は, 同じ内容を(より)グラフィカルに表現するか否かの差に過ぎず, 機能的には全く同等であるべきなのだ. 従ってフォールバックコンテンツにリンクやinput要素等のフォーカス移動が可能な内容が存在する場合は, canvas要素が有効な環境においてもあなたが責任を持って同等の機能を実装すべきなのである.

この観点からcanvas要素のAPIにはこのUI機構の実装をサポートするための仕組みが提供されている. 例を示す. 以下はcanvas要素を用いてフォーカス移動処理を実装したものだ.

<!--canvas要素の記述-->
<canvas id="focusable">
このカンバス要素には<span id="rect" tabindex="0">四角</span>と<span id="circle" tabindex="0">丸</span>と<span id="triangle" tabindex="0">三角</span>の3つの図形が描かれています. 
</canvas>
このカンバス要素には四角三角の3つの図形が描かれています.

フォールバックコンテンツのスタイルを参照する

canvas要素配下のフォールバックコンテンツは直接描画されないだけでDOM内部で生きている. 従ってグラフィックを描画する際に直接色を指定するのではなく, フォールバックコンテンツの内容を参照するようにすると, 文書全体の印象をcanvas要素の有効/無効に依存せずCSSのみで制御することが可能となる.

div in canvas
//incanvasに設定されているスタイル #incanvas{ color: white; font-weight:bold; font-size: 20px; font-family:sans-serif; background-color:navy; }

カスタムコントロールの構築

先ほど見たとおりフォーム部品をcanvas要素で囲んでカスタムコントロールとすることができる. canvas要素が動作しない環境でも元のフォーム部品が表示されることでWEBページの機能が維持されることに着目しよう. 例を示す. canvasをクリックし, 矢印キーを使って値を変更すると, その内容でグラフィックが書き換わる.

フォーカスリングの描画

コントロールにフォーカスが存在していることを表すにはグラフィックの内容を書き換えただけでは不十分だ. drawFocusIfNeededメソッドを用いてフォーカスがどこにあるのかをブラウザ側に伝える必要がある. こうすることでフォーカスがあたった際の自動スクロール等が動作するようになる.

出典:canvas要素のフォーカス領域を指定する動作サンプル:Accessible canvas clock※ですが, 現在はAPIが古くなっており動作しません.

CanvasRenderingContext2D.drawFocusIfNeeded()
フォーカスリングを描画する. 引数にElementを指定すると, その要素がフォーカスを持っている場合に, 現在のパス図形に沿ってフォーカスリングを描画する.

ヒット領域の設定(tobe)

ヒット領域はcanvas要素における仮想的な子要素と言える概念で, 単体で存在することもフォールバックコントロールに結びつけることもできる. ヒット領域を用いるとcanvas上で発生したイベントをヒット領域毎に振り分けることが可能となる.

CanvasRenderingContext2D.addHitRegion(params)
ヒット領域を追加する. カンバスに擬似的に子要素として振る舞う領域を追加する. 引数にはパラメータセットを指定する.
path
ヒット領域とするパス図形. 省略するとコンテキスト中のパス図形を用いる.
fillRule
ヒット領域の定義ルール. nonzero/evenoddの何れかを指定する.
id
ヒット領域に対する識別子. canvas要素の当該ヒット領域上でMouseEvent/TouchEventが発生した際, イベントオブジェクトのregionプロパティにこの値が設定される. 未指定の場合, 下記のcontrolを指定する必要がある.
label
ヒット領域に対応するcontrolが存在しないケースに対するヒット領域に対するラベル.
role
ヒット領域に対応するcontrolが存在しないケースに対する視聴覚ロール(ARIA role)
parentID
ヒット領域に対する親ヒット領域のIDを指定する. (ヒット領域を階層化できる)
control
ヒット領域に対応する要素. canvas要素配下のフォームオブジェクトに限られる. ヒット領域上で発生したイベントはcontrol要素上で着火したものとして扱われる.
cursor
ヒット領域上でのマウスカーソルの種類.
CanvasRenderingContext2D.removeHitRegion(id)
指定したidの要素に対するヒット領域を削除する.
CanvasRenderingContext2D.clearHitRegions()
ヒット領域を全て削除する.
MouseEvent.region/TouchEvent.region
イベントを発した領域が設定されている.

ヒット領域が定義されたcanvas要素では, その上で発生したMouseEventのregionプロパティにヒット領域のIDが設定される.

IDの代わりにcontrolを指定しておくと, イベント発生時にcontrolでイベントが発生したものとして扱われる.

18・その他の機能

パス図形と座標の包含関係の判定

現在カンバスが保持しているパス図形と座標(≒canvas要素の画素位置)の包含関係を確認できる. 例えばある領域にマウスカーソルが当たった際にカンバスの内容を書き換えると言った用途に使える. 応用次第で様々な場面で使える機能となるだろう.

CanvasRenderingContext2D.isPointInPath(x,y[,fillRule])or(path,x,y[,fillRule])
指定した点が現在のパス図形の領域内か判定する.
CanvasRenderingContext2D.isPointInStroke(x,y)
指定した点が現在のパス図形のストローク領域内か判定する.

transformメソッド等により現在の描画用の座標軸には影響を受けず, カンバスそのものが持つ(左上を原点とした)座標系を元に判定する. よって引数に与える値はこの座標系に基づいたものを渡す必要が有る. また, カンバスが保持できるパス図形は一つであるが, スクリプト内部でHTMLCanvasElementオブジェクトを保持することで, 複数の領域について座標の判定を行うことができる.

例を示す. この例では内部の矩形にマウスカーソルを当てることで, 塗り潰しの色・線の色を変化させている.

Path2Dオブジェクトと組み合わせる

Path2Dオブジェクトをサポートする環境であれば, パス図形毎に領域判定が可能である.

WEBページの自動スクロール

scrollPathIntoViewメソッドを用いるとWEBページを自動的にスクロールさせ, 当該パス図形範囲をスクリーン上に表示させる事ができる.

CanvasRenderingContext2D.scrollPathIntoView()
現在のパス図形を表示できる位置までWEBページをスクロールする.

19・高解像度環境での動作

出典:High DPI Canvas

canvas要素とスクリーン解像度

Retinaディスプレイのようにスクリーン環境の機能が向上するにつれ, WEBでのピクセル値の扱いも変化している. つまり1px平方を複数のスクリーン画素によって描くことで, これまでよりも精細なグラフィックが描けるようになった. その反面, canvas要素はサイズをピクセル値で指定するために互換性の点で非常に厄介な問題が発生している.

デバイスピクセル比とcanvas内部ピクセル比

高解像度環境でcanvas要素を扱う場合, デバイスピクセル比とcanvas内部ピクセル比の2つを考える必要がある. 前者は1ピクセルあたりのスクリーンデバイス上での画素数を表し, 後者はcanvasグラフィックの内部で1ピクセルあたりに確保される画素(グラフィックの最小分解単位)数である.

これらは次のAPIで取得できる.

window.devicePixcelRatio
デバイスピクセル比. undefinedの場合は1(devpx/px)
CanvasRenderingContext2D.webkitBackingStorePixcelRatio
canvas内部ピクセル比. Safari専用(bspx/px)

この内容は環境(OS,ブラウザ)で大きく異なるので, 同じスクリプトを発行したとしても, 必ずしも環境毎に最適化された出力が得られないのだ.

ピクセル比を加味したグラフィックの描画

canvas要素で描いたグラフィックが最も美しくスクリーン上に表示されるには, canvas内部の1画素がスクリーン上の1画素に対応する場合である. そのため事前に絶対ピクセル比(canvas最小分解単位あたりのデバイスピクセル数)を求めておき, その結果に応じてカンバスサイズを広げ, scaleメソッドを使って座標スケールを調整しておくとよい. こうすることでメインとなる描画処理に一切手を入れること無く, 動作環境毎に最適なグラフィックを描くことができる.

☆絶対ピクセル比 = デバイスピクセル比 ÷ canvas内部ピクセル比(devpx/bspx)
//idiom
var devicePixelRatio = window.devicePixcelRatio || 1;
var backingStorePixcelRatio = ctx.webkitBackingStorePixcelRatio || 1;
var ratio = devicePixelRatio/backingStorePixcelRatio;

高解像度ピクセルデータの取得

Safari環境ではwebkitBackingStorePixcelRatioに応じて1ピクセルが複数の画素データをもつため, 通常のgetImageDataではなく高解像度環境専用のピクセルデータ取得APIが定義されている.

CanvasRenderingContext2D.webkitGetImageDataHD()
高解像度ピクセルデータを取得する
CanvasRenderingContext2D.webkitPutImageDataHD()
高解像度ピクセルデータを挿入する

20・CSSとcanvas

canvas要素と背景

canvas要素にも背景画像を定義することが出来る. つまり, canvas要素は実質静的なスクリーンと動的なスクリーンの2層構造をとる. 従って静的な部分をCSS背景画像としておき, (アニメーション等の)可変な部分のみをcanvas要素本体に描画すると言った使い分けをすることで, グラフィック描画部の構造がシンプルに保たれる.

canvasグラフィックを背景に用いる

先の概念を一歩進め, canvas要素で描いた内容を何らかの要素の背景とすることを考える. この場合, グラフィックの内容を一旦データURIスキーム形式もしくはBlobURIスキーム形式としてCSSプロパティに静的に設定する必要がある. しかし, ブラウザによっては独自機構を用いる, もしくは次期CSS仕様を先取りすることで背景画像に直接canvas機構で描いたグラフィックを設定することが可能だ. 何れにせよ標準仕様が現在策定中であるため, 使う場合は注意を要する. (Internet Explorerにはこれらに類するものは存在しません.)

CSS背景によるcanvasグラフィック表示のメリット

CSSとcanvas要素とを組み合わせると次のようなメリットが得られる.

element関数+CSS.elementSourcesを用いる(w3c標準)

background-image:element(#id)
CSS4のelement関数を用いて指定したIDの要素のグラフィックを背景画像とする.
CSS.elementSources.set(id, element)
DOMオブジェクトをグラフィック参照先として登録する. CSS4で検討されている.

Gekko系ブラウザ(FireFox等)

background-image:-moz-element(#id)
指定したIDの要素のグラフィックを背景画像とする(CSS4のelement関数に相当する). 従って, canvas要素を参照することで動的な背景とすることが可能となる.
document.mozSetImageElement(id, canvas);
documentにCSSの-moz-element関数中から参照可能なHTMLCanvasElementオブジェクトを追加する. FireFoxの独自機能.

旧webkit系ブラウザ(Safari等)

background-image:-webkit-canvas(id)
背景画像をcanvasによるグラフィックとする. idは下記メソッドにおける識別子を指定する.
document.getCSSCanvasContext(type, id, width, height)
内蔵canvasに対応するコンテキストオブジェクトを取得する.

CSS Shapesとcanvas要素

CSS ShapesはCSSで扱う図形の概念で, テキストの回り込みと言った用途に用いられる. しかし標準的な図形が矩形, 楕円, 多角形に限られており表現力に欠ける. 一方, 図形の指定に画像データを渡すことが出来るため, canvas要素で描いた図形を元に自由なテキストレイアウトが可能となる.

shape-outside
要素の外形を指定する. floatプロパティと組み合わせることで, テキストの回り込みを自由に設定できる. url関数を使って画像データを渡すことが出来るのでtoBlobメソッドを使ったcanvasグラフィックを渡すことで任意のテキスト回り込みを定義できる.
このdiv要素にはcanvas要素で描いたグラフィックによるテキストの回り込み設定が施されています. shapeOutsideプロパティにtoBlobで得たグラフィックを設定することで基本図形だけでは表現出来ない自由なテキストレイアウトを実現しています. (Chromeのみで動作を確認)

21・canvas要素によるリソースの占有

ポインターイベントとcanvas要素

マウスカーソルが発するポインターイベントは通常スクリーン上のカーソル位置を規準に生成される. そのため, canvas要素が受け取れるイベントはcanvas要素の描画範囲に限られる. 一方でcanvas要素は逐次内容を書き換えることで内部で無限の広がりを表現することが出来るため, 単一方向への継続的な移動と言った操作が発生しうる. 例えば地図の表示範囲を変更する操作がこれにあたる. 従って先の範囲限定的なイベント取得ではcanvas要素からカーソルが外れることで操作が途切れてしまうし, フルスクリーン環境ではスクリーン端にカーソルが到達したらそれ以上移動できないということになる.

この問題を解決するには幾つか方法があるが, ここではWEBブラウザが提供しているPointer Lock APIを用いてcanvas要素がマウスカーソルを占有する方法を示す.

Pointer Lock APIとは

Pointer Lock APIは特定のHTML要素に対してポインターイベントを占有させる仕組みで, カーソル位置の代わりに新たにカーソルの移動量を取得可能とする. そのため, ノードの描画位置やスクリーン範囲等の境界値に依存しない継続的なイベント検知が可能となる. なおカーソルの位置の管理をポインター占有側に移譲するため, 副次的にブラウザによるカーソルは表示されなくなる. これらの特性から事実上canvas要素のための仕組みと言って良い.

element.requestPointerLock()
ポインターの占有(ポインターイベントの発生箇所の固定)を開始する. 占有を解除するにはESCキーを押下するもしくはexitPointerLockメソッドを実行する. ポインター占有中はclientX/Y等のカーソル座標の内容が固定される(意味がなくなる).
document.pointerLockElement
ポインターを占有している要素を取得する.
document.exitPointerLock()
ポインターの占有を終了する.
document pointerlockchangeイベント
ポインターの占有状況が変化した際に発生する.
document pointerlockerrorイベント
ポインターの占有操作に失敗した際に発生する.
mouseevent.movementX/movementY
ポインターの移動量(前回のmousemoveイベント発生時からの差分値)を取得する.

例を示す.

canvas要素のフルスクリーン化

ポインターの占有と同様にFullscreen APIを用いると特定の要素をスクリーン全体に広げて描画することが出来る.

element.requestFullscreen()
当該要素をフルスクリーン化する.
document.fullscreenElement
フルスクリーン化している要素を取得する.
document.fullscreenEnabled
フルスクリーン化可能かどうかを判定する.
document.exitFullscreen()
フルスクリーン動作を終了する
document fullscreenchangeイベント
フルスクリーンの状況が変化した際に発生する.
document fullscreenerrorイベント
フルスクリーン操作に失敗した際に発生する.

一般にスクリーンのサイズは環境によって異なるため, canvasサイズの調整が必要となる. 従ってフルスクリーン化前後でグラフィック内容を維持する場合はどのように実現するかが問題となりうる.

例を示す.

なお, canvas要素そのものではなく, フルスクリーンを解除するコントロールとcanvas要素とをひとまとめにしたものをフルスクリーン化すべきかもしれない. なぜなら, キーボードによるフルスクリーンの解除はタブレット等のキーボードレスの環境で問題となりうるからだ(ソフトウェアキーボードを表示する手間をユーザーに強いるのは不適切である). もちろんアプリケーション側でフルスクリーン解除の仕組みを提供する場合はこの限りではない.

22・ブラウザ環境の保護

リソースの強制開放

出展:Canvas Context Loss and Restoration
参考:Canvas が専有するリソースをパージ可能になる Canvas Context Loss and Restoration について

WEBブラウザに表示するコンテンツがリッチになるにつれ, 要求されるハードウェアリソース(CPU/GPU資源やメモリ)の量も年々増加傾向にある. これはcanvas要素においても例外ではなく高機能・高精細化の代償として, 消費されるリソースもかなりの分量となってきている. とは言え, 全ての環境がWEBページの要求する条件を満たせるわけではない. そのため高負荷なWEBページを公開した際, スペック的に見劣りする環境からのアクセスを考慮しなかった場合, ブラウザはおろかOSごとクラッシュしてしまうユーザビリティ的に最悪なケースも発生しうる.

このような問題は一般にメモリ管理の拙さやチューニング不足と言ったスクリプトの質の問題として片付けられてしまうものの, 時には意図しないバグの混入により発生しうる. そのため, 致命的な状態に陥る前に占有しているリソースを強制開放させ, ブラウザ環境を保護することは原因の究明を円滑に行う目的においても理に適っている. もちろんアプリとしての機能は損なわれてしまうが, OSを巻きこむことに由る不測の事態と比べれば微々たる問題と言える.

これはそもそもWebGL環境における要望事項だったが, blinkはこのアイディアをCanvasRenderingContext2Dにおいても利用可能としている.

ctx.getContext("2d", {storage: "discardable"})
コンテキストを破棄可能とする. システムに負荷がかかった際, 強制的にコンテキストを破棄しcontextlostイベントを発生する.
ctx.getContext("2d", {storage: "persistent"})
逆にリソースの開放を一切しない(つまりこれまでどおりの用途とする)
ctx.isContextLost()
現在のコンテキストオブジェクトが破棄させられているかの判定値. trueの場合, コンテキストオブジェクトに対する一切の操作が不可能となる.
contextlostイベント(ctx.oncontextlost?)
コンテキストオブジェクトが破棄された際に実行する処理を設定する.
contextrestoredイベント(ctx.oncontextrestored?)
コンテキストオブジェクトが復帰した際に実行する処理を設定する.

23・補足)ExplorerCanvasを利用する

ExplorerCanvasの動作原理

レガシーIE上でHTML5のcanvas要素をエミュレートさせるExplorerCanvasは, 各種メソッドに対する操作をVML(Vector Markup Language)に置き換えている. 従って生成されるグラフィックは厳密にはベクタ形式の画像となる. また, ライブラリが作られてから時間が経過しているため, 対応していないAPIも多々有る.

何れも魅力的な機能であるが, 残念ながらExplorerCanvasでは利用することができない. また, メソッドを実行する毎にDOM上にVML要素に相当するオブジェクトが蓄積していくため, 余り凝ったことをした場合, ブラウザへの負荷が懸念される. 従ってExplorerCanvasを介したcanvas要素の利用はグラフの描画など, あくまでサポート的な利用に留めておくべきだ. この点でどうしてもグラフィカルな処理が必要というのであれば, 無理にcanvasを利用するのではなく, 素直にFlashの利用を検討するなど既存技術をベースに検討したほうが良い.

ExplorerCanvasの導入

ExplorerCanvasを利用するには他のJavaScriptライブラリと同様, HTMLのhead要素の中で次の宣言をすれば良い.

<script type="text/javascript" src="excanvas.compiled.js"></script>

これだけでcanvas要素にアクセスする準備は整った. 他の環境と同じくgetElementByIdメソッド等でcanvas要素に相当するオブジェクトを取得し, 各種描画メソッドを実行してグラフィックを描くことができる. が, 細かい部分での注意点が存在する. いずれもcanvas要素そのものの操作に関わるものなので, 動的に要素を生成するケースを想定している場合は注意して欲しい.

実際の動作についてはこちらを参照のこと.