19・Raphaëlを用いたベクタグラフィック
svgの持つ標準apiを使うだけでもそれなりの処理を行うことができるが,Raphaël—JavaScript Library を用いることでsvgが動作しないieにおいても擬似的にsvgを描画できるようになる.また,svgを描画する目的であっても非常に優秀である.ここではRaphaëlの使い方について紹介する.なお,執筆時のバージョンは2.1.0のため,最新の情報については配布サイトを参照されたい.※このページではRaphaëlの動作サンプルを掲載しています.記述されているコードを実際に動作させているため,若干ロードが遅いのでご注意下さい.なお,ie8ではvmlでの動作が確認できます.
※ie環境を対象としないのであれば,合わせてSnap.svg を検討してみましょう.svgのみをターゲットとしていることからより高度な処理が可能となります.
Raphaëlの機能
Raphaëlはwebブラウザでベクタグラフィックを記述する為のjavascriptライブラリであり,html文書内部での利用を想定している.MIT Licenseで配布されているため,ライセンス的にも利用しやすい.ここでsvgと限定していないのは,ベクタ画像を出力するためにブラウザにより出力形式をsvgとvml(internet explorerの場合)とに自動的に振り替えている ためである.この動作のためプログラマーはブラウザの種類を意識すること無く,単一のインターフェースを操作するだけで済む.従って,Raphaëlがサポートする機能は基本的にsvgとvmlの共通部分をベースとしているが,独自に実装している機能は驚くほど強力である.
基本図形の描画
グラデーションの設定
図形要素の管理
ラスタ画像の挿入
テキストの挿入
アニメーションの実行
イベントの捕捉
以下はRaphaëlが独自に提供している機能だ.
簡略化されたマーカー・線の装飾
強力な図形変換処理(ベジェ曲線の変換,分割,描画領域の取得など)
強力な色の変換処理(rgb,hsb,hsl色空間のサポート)
Cufón 形式のフォント描画
eve による柔軟なカスタムイベント処理
汎用的なメソッドの提供
svgの持つクリップ・マスク操作,フィルター操作等の画像の合成に関わるものは簡易的な物を除いて利用できないが,それを補って余りある機能を有しているのだ.また,一般に冗長となりがちなsvgdom操作ではあるが,Raphaëlを用いることでjsonを用いた図形の一括指定が出来るようになるなど,javascriptの持つ強力な構文機構を利用して簡潔にコード記述できるようになる .従って,Raphaëlをsvgを描画する為のライブラリとして利用することも出来る .
注意すべき点としてはRaphaëlが既存のsvg要素の内容にアクセスする機能は持っていない 点である.従って,illastrator等で描画した内容をそのままRaphaëlで読み込むと言った用途には向かず,もっぱらグラフやダイアグラムを動的に作るといった場面で威力を発揮することとなるだろう.なお,ユースケース的には十分に有り得るため,有志によりraphael-svg-import と言ったプラグインや,SVG2Raphael といったものが提供されている.本項でもxslを使った簡単な変換ツールを紹介する.
Raphaëlの構成
Raphaëlが提供しているオブジェクトは次の8つである.数が少ないので,それほど複雑ではない.
Raphaelオブジェクト
Raphaëlを構成する最も基本となるオブジェクトである.ベクタグラフィックの描画領域の定義の他,各種ユーティリティ機能を提供する.
Paperオブジェクト
ベクタグラフィックを描画する領域を表すオブジェクトであり,svgにおけるsvg要素に相当する.図形を描画するための機能を提供する.
Elementオブジェクト
基本図形を表すオブジェクトであり,色や,見た目の制御を行う為のapiを提供する.
Setオブジェクト
基本図形のグループ化を表すオブジェクトであり,スタイル属性の一括設定や,様々な配列操作を提供する.
Animateオブジェクト
図形に対するアニメーションを定義するオブジェクト.
Matrixオブジェクト
transform処理を行う際の行列演算をサポートする機能を提供する.
eveオブジェクト
カスタムイベントを定義・実行するライブラリ.
fontオブジェクト
Cufón形式のフォントデータを格納するオブジェクト.
オブジェクトの相関を表すと次のようになる.
Raphaëlによるグラフィックの描画手順
Raphaëlを利用するには他のjavascriptライブラリと同様にhtmlのheader要素内部で
「<script type="text/javascript" src="raphael-min.js"></script>
」 と記述する.jQueryとの併用も可能だが,組み合わせて利用するケースは殆ど無いと思われる.
グラフィックを描画する手順は次のとおりである.
グラフィックを描画したい部分をdiv要素で定義しておく.
このdiv要素を元にpaperオブジェクトを取得する.
paperオブジェクトを元に図形オブジェクトを生成する.
図形オブジェクトに対してスタイルを設定する.
下の例はidにcanvas1を持つdiv要素にグラフィックを描いた例である.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas1", 200, 200); //下記のようにHTMLDivElementを渡しても良い. //var paper = Raphael(document.getElementById("canvas1"), 200, 200); var circle = paper.circle(100, 100, 80); circle.attr("fill", "#f00"); circle.attr("stroke", "orange"); circle.attr("stroke-width", 10); });
グラフィック描画メソッドとしてsvgが選択された場合,このdiv要素内部にsvg要素が配置される.一方vmlが選択された場合,vmlは明確なコンテナ要素を持たないため,div要素に直接vmlの各種要素が配置される.cssでの見た目を調整する場合はsvg要素にはスタイルを設定しないほうが無難であろう.なお,div要素を指定せず,body要素における絶対位置で指定する方法もある.また,Raphaelにファンクションオブジェクトを登録することで,jQueryと同様にdomの解析が完了したタイミングで処理を実行することが出来る.
Raphael
document.ready時に処理を実行する.Raphael(func)
Paperオブジェクトを生成する(div要素に描画する).Raphael(targetDivId, width, height)※HTMLDivElementを渡しても良い.
Paperオブジェクトを生成する(body要素に直接描画する).Raphael(x, y, width, height)
以下,Raphaëlの使い方についてひと通り解説するが,いかんせん統一された情報に乏しく,公式配布サイトのapi表 も御世辞にも親切とは言えない.従って筆者独自の解釈が多分に含まれており,実際の設計思想と異なる場合があることを予めお詫びする.なお,公式サイトにおいては具体的な応用例について多数公開されており,ある程度まではその思想を汲むことは可能である.なお,ライブラリの不具合により正常に動作しないものもあるので注意のこと.
カンバスサイズ・ビューボックスの設定
Paperオブジェクトにはベクタグラフィックスの表示範囲を定義する関数setViewBoxが定義されている.これはsvgにおけるviewBoxに相当する機能である.
Paper.setViewBox
グラフィックの表示範囲の指定.
setViewBox(x,y,width,height,fit)(falseの場合左上を基準とする.trueの場合引き伸ばされる.)※古いfirefoxでは正しく動作しない
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas1_1", 200, 200); paper.setViewBox(0,0,300,200,true); var rect = paper.rect(0,0,300,200); var circle = paper.circle(100, 100, 80); circle.attr("fill", "#f00"); circle.attr("stroke", "orange"); circle.attr("stroke-width", 10); });
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas1_1_2", 200, 200); paper.setViewBox(0,0,300,200,false); var rect = paper.rect(0,0,300,200); var circle = paper.circle(100, 100, 80); circle.attr("fill", "#f00"); circle.attr("stroke", "orange"); circle.attr("stroke-width", 10); });
また,setSizeメソッドでカンバスのサイズを後から変更することも出来る.※下の例ではsvg要素におけるスタイル値が有効となっているので,見た目大きさが変わっていません.
Paper.setSize
描画カンバスの大きさを変更する.setSize(width,height)
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas1_2", 200, 200); paper.setSize(300,300); var circle = paper.circle(100, 100, 80); circle.attr("fill", "#f00"); circle.attr("stroke", "orange"); circle.attr("stroke-width", 10); });
Raphael.setWindowメソッドを用いるとフレームを跨いだ描画も可能だ.iframeのもつcontentWindowオブジェクトを渡すことで,iframe内のdiv要素に図形を描画することが出来る.
Raphael.setWindow
ベクタグラフィックの描画対象をiframeにする.setWindow(iframe.contentWindow)
Raphael(function(){ try{ var iframe = document.getElementById("raphael_iframe"); iframe.contentWindow.document.body.innerHTML = "iframe"; //描画対象のウインドウを変更する. Raphael.setWindow(iframe.contentWindow); var paper = Raphael(0,0, 200, 200); var circle = paper.circle(100, 100, 80); circle.attr("fill", "#f00"); circle.attr("stroke", "orange"); circle.attr("stroke-width", 10); }catch(e){ }finally{ //描画対象のウインドウを元に戻す. Raphael.setWindow(window); } });
基本図形の描画
Paperオブジェクトには各種図形を生成するメソッドが提供されている.
図形オブジェクトを生成するメソッド
実行するとElementオブジェクトを生成する.
Paper.circle
円を描画する.circle(x,y,r)
Paper.ellipse
楕円を描画する.ellipse(x,y,rx,ry)
Paper.image
ラスタ画像を描画する.image(src,x,y,width,height)
Paper.path
パスを描画する.path([pathString:d操作の内容])
Paper.rect
矩形を描画する.rect(x,y,width,height)
Paper.text
文字列を描画する(初期設定:中央揃え).text(x,y,text)
Created with Raphaël 2.1.0 It's Raphaël! とても便利!
Raphael(function(){ var paper = Raphael("canvas2", 200, 200); //円 var circle = paper.circle(50,50,25); circle.attr("fill", "red"); //楕円 var ellipse = paper.ellipse(150,50,40,25); ellipse.attr("fill", "orange"); ellipse.attr("href", "#"); //ラスタ画像 var image = paper.image("img.png", 10,80,100,50); //線(パス) var path = paper.path("M 120,80 L 180,120"); path.attr("stroke-dasharray","-"); //矩形 var rect = paper.rect(20,160,50,30); rect.attr("r", 5); //文字列(\nで改行することも出来る.) var text = paper.text(130,170,"It's Raphaël!\nとても便利!") .attr({"font-size":20, fill:"none" , stroke: "blue"}); });
生成した図形オブジェクト(Elementオブジェクト)に,attrメソッドで属性を設定していく.引数としてjson形式の設定情報を渡すこともできるが,jQueryと異なりcamel形式での指定はできない.つまり「stroke-width」を設定する場合は,キー文字列として「strokeWidth」を使用することは出来ず,二重引用符で囲った"stroke-width"を指定する必要がある.図形オブジェクトに設定可能な属性は次の通りである.図形要素によって有効な属性が異なる.またRaphaël独自の記述法が存在するものは後ほど解説する.
Element.attr
属性値の設定/取得.attr([属性値名],[設定値])もしくはattr(json)で値の設定.attr([属性値名])で値の取得.
【図形の描画基準座標に関わる属性】※†付きはRaphaël独自の記述法で行うもの.
x,y
図形の描画基準座標.テキストの描画基準座標.
width
図形の幅.
height
高さ.
cx,cy
円・楕円の中心.
r
円の半径及び矩形の四隅の丸め半径.
rx,ry
楕円の半径.矩形の四隅の丸めには対応していない .
【パス専用の属性】
path†
パスを定義する操作.svgにおけるpath要素のd操作に相当する.
arrow-end†
パスの終点マーカー.[スタイル]-[幅(narrow/midium/wide)]-[長さ(short/midium/long)]
arrow-start†
パスの始点マーカー.[スタイル]-[幅(narrow/midium/wide)]-[長さ(short/midium/long)]
【塗りつぶしに関わる属性】
fill†
塗りつぶしの色.
fill-opacity
塗りつぶしの不透明度.(1で不透明,0で透明)
【線の描画に関わる属性】
stroke†
線の色.
stroke-dasharray†
破線の設定.独自形式での指定.例「- 」
stroke-linecap
線の端点のスタイル.(butt無し|round丸め|square四角)
stroke-linejoin
線の頂点のスタイル.(miter角|round丸め|bevel面取り)
stroke-miterlimit
線の頂点の尖りの上限値.
stroke-opacity
線の不透明度.(1で不透明,0で透明)
stroke-width
線の太さ.
【テキストの描画に関わる属性】
text†
テキストの内容.\nで改行も可能.
text-anchor
テキスト描画の基準.
font
フォントの設定.
font-family
フォントファミリーの設定.
font-size
フォントの大きさ.
font-weight
フォントの太さ.
【図形の描画に関わる属性】
blur
図形のぼかし幅.
clip-rect†
図形の描画を制限する範囲.x,y,width,height
opacity
図形の不透明度.(1で不透明,0で透明)
transform†
図形の変形.独自記述.
【リンクに関わる属性とその他の属性】
cursor
カーソルの指定.
href
図形クリックした際のリンク先.
src
ラスタ画像の参照先url.
target
リンクの表示先設定.(_top|_blank|[name])
title
図形のタイトル.
Element.type
図形オブジェクトのタイプ.circle,ellipse,image,path,rect,textの何れかが設定される.
図形の一括指定とSetオブジェクト
図形の描画,プロパティの設定はaddメソッドで簡略化することが出来る.
Paper.add
図形を一括描画する.Setオブジェクトを返す.add([json notation]).
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas3", 200, 200); //addメソッドではjson形式での一括指定が可能. paper.add([ { type: "rect", x: 0, y: 0, width: 200, height: 200, fill: "yellow" }, { type: "circle", cx: 70, cy: 70, r: 50, fill: "red" }, { type: "rect", x: 80, y: 80, width: 80, height: 80, fill: "blue", "fill-opacity": 0.5 } ]); });
addメソッドを実行すると,その処理結果としてSetオブジェクトが返される.このオブジェクトに施した操作は,内部の画像要素の全てに適用される.Setオブジェクトを生成する方法としては,Paper.setメソッドを実行する Paper.addメソッドを用いる Paper.setStartメソッドを実行した後で,Paper.setFinishメソッドを実行する の何れかを行う.Setオブジェクトは一見svgのg要素によるグループ化に似ているものの,あくまでスクリプト処理の簡略化を目的とした機構 である.Raphaëlが生成するdomは,全ての図形要素が同じ階層に存在するフラットな構造 をとる.
Paper.set
Setオブジェクトを生成する.set().引数に図形オブジェクトを渡すことも可能.
Paper.setStart
Setオブジェクトの開始を宣言する.setStart().
Paper.setFinish
Setオブジェクトを取得する.setStartメソッド以降,Paperオブジェクトで描画した図形要素のリストを取得する.setFinish().
Set.attr
Set内部の図形要素に一括で属性値を設定する.
Set.type
文字列「Set」が設定されている.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas3_1", 200, 200); var set = paper.add([ {type: "circle",cx: 70,cy: 70,r: 50}, {type: "rect",x: 80,y: 80,width: 80,height: 80} ]); set.attr("fill", "red"); });
Setオブジェクトのもつメソッド
Setオブジェクトにはその他リスト操作に関わるメソッドが提供されている.
Set.clear
中身を空にする.
Set.exclude
指定した図形オブジェクトを削除する.
Set.forEach
Setオブジェクトに属する全ての図形オブジェクトに処理を行う.forEach(callback,thisArg).callback関数内でfalseを返すとループ処理が終了する.thisArgはcallback関数内部においてthis変数から参照できる.
Set.pop
図形オブジェクトをポップする.
Set.push
図形オブジェクトを追加する.
Set.splice
図形オブジェクトを入れ替える.splice(index,count,[挿入する図形オブジェクト]).
Set.items
内部の図形オブジェクトの配列.
Set.length
内部の図形オブジェクトの数.
Set.clone
Setオブジェクトを複製する.
forEachメソッドの例を示す.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas3_2", 200, 200); var set = paper.add([ {type: "circle",cx: 70,cy: 70,r: 50}, {type: "rect",x: 80,y: 80,width: 80,height: 80} ]); set.forEach(function(elem, i){//このように引数経由で図形要素とインデックスが渡される if(i == 0){ elem.attr("fill", this.color_A); }else{ elem.attr("fill", this.color_B); } },{color_A:"green", color_B:"blue"}); });
同様の機能はPaperオブジェクトにも定義されている.
Paper.forEach
Paperオブジェクトに属する全ての図形オブジェクトに処理を行う.
色の指定方法
Raphaëlではhtml定義色やrgb関数による指定の他,様々な方法で色を設定することが出来るため,柔軟な色の指定が可能となっている.色相は360°に対する割合で,彩度・明度・輝度は0〜1の割合で指定する.色相角に対応しているため,やや力技ではあるが,円環状のグラデーションを実現できる.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas9_2", 200, 200); var i; for(var i = 0; i<144; i++){ paper.path("M 97,0 h 6 L 101,101 L 99,101 z").attr({ //ちょっとずつ色を変化させ,重ねていく. fill: "270-hsb("+i/144+",1,1):25%-white:75%", stroke:"none", "stroke-width": 0, transform: "r" + (360/144*i) + ",100,100" }); }; });
color name
htmlで定められている色名称
#fff/#ffffff
16進数指定
rgb(,,)
rgb形式
rgba(,,,)
rgba形式
hsb(,,)
hsb色空間(色相Hue・彩度Saturation・明度Valueでの指定)
hsba(,,,)
hsb色空間(色相Hue・彩度Saturation・明度Value+不透明度Alphaでの指定)
hsl(,,)
hsl色空間(色相Hue・彩度Saturation・輝度Luminanceでの指定)
hsla(,,,)
hsl色空間(色相Hue・彩度Saturation・輝度Luminance+不透明度Alphaでの指定)
色の相互変換
Raphaelオブジェクトではこれらの色空間の記述形式を相互に変換するメソッドを提供している.
Raphael.getRGB
色の名称/記法からrgb色空間の色{r,g,b,hex,error}に変換する.getRGB(color)
Raphael.hsb
hsb色空間の色を16進表記の色に変換する.hsb(h,s,b)
Raphael.hsb2rgb
hsb色空間の色をrgb色空間での値{r,g,b,hex}に変換する.hsb2rgb(h,s,b)
Raphael.hsl
hsl色空間の色を16進表記の色に変換する.hsl(h,s,l)
Raphael.hsl2rgb
hsl色空間の色をrgb色空間での値{r,g,b,hex}に変換する.hsl2rgb(h,s,l)
Raphael.rgb
rgb色空間の色を16進表記の色に変換する.rgb(r,g,b)
Raphael.rgb2hsb
rgb色空間の色をhsb色空間での値{h,s,b}に変換する.rgb2hsb(r,g,b)
Raphael.rgb2hsl
rgb色空間の色をhsl色空間での値{h,s,l}に変換する.rgb2hsl(r,g,b)
なお,fillメソッド等で空文字列を指定した場合,無色(none)として扱われる点に注意が必要だ.
色の自動生成
また,階段状の色の変化を表現するためにgetColorメソッドが提供されている.グラフなどで使うと美しいグラデーションを実現できるだろう.
Raphael.getColor
赤を基準に次の色を取得する.引数には明るさを0〜1の範囲で渡す.14×6(84)回の操作で元の色に戻る.getColor(brightness)
Raphael.getColor.reset
色のリセットを行う.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas9", 200, 200); var i,j; for(i = 0; i<20; i++){ paper.add([{type: "rect",x: 10*i,y: 0,width: 10,height:50,fill: Raphael.getColor()}]); } Raphael.getColor.reset(); for(i = 0; i<20; i++){ paper.add([{type: "rect",x: 10*i,y: 50,width: 10,height:50,fill: Raphael.getColor(1)}]); } Raphael.getColor.reset(); for(j = 0; j<12; j++){ for(i = 0; i<14; i++){ paper.add([{ type: "rect", x: 200/14*i,y: 100+100/12*j,width: 200/14,height:100/12, fill: Raphael.getColor(1), stroke:"none","stroke-width":0}]); } } });
グラデーションの設定
図形オブジェクトのfill属性にグラデーションを設定することが出来る.css3におけるグラデーションの指定に近く,シンプルに実装できる.
記述法:[角度]-[color-stop]-[color-stop]
※color-stop値は[color]:[offset%]
とする.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas4", 200, 200); paper.add([ { type: "rect", x: 10,y: 10,width: 80,height: 80, fill: "0-red-blue" }, { type: "rect", x: 110,y: 10,width: 80,height: 80, fill: "45-red-blue:25%-green" }, { type: "rect", x: 10,y: 110,width: 80,height: 80, fill: "0-red-blue:50%-red:50%-green" }, { type: "rect", x: 110,y: 110,width: 80,height: 80, fill: "0-red-blue:25%-red:25%-green", "fill-opacity": "0" } ]); });
グラデーションを設定したオブジェクトに対しopacity属性を設定すると,不透明度にグラデーションが掛かる (正常な動作なのかvmlの制限なのか不明).
放射状グラデーション
circle及びellipseに限られるが,放射型グラデーションも設定できる.その際,焦点を設定することも可能である.
記述法:r ([焦点の位置0〜1x],[焦点の位置0〜1y])[color-stop]-[color-stop]
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas5", 200, 200); paper.add([ { type: "circle", cx: 50,cy: 50,r: 40, fill: "r white-black" }, { type: "circle", cx: 150,cy: 50,r: 40, fill: "r(0.25,0.25)white-black" }, { type: "ellipse", cx: 50,cy: 150,rx: 40, ry: 30, fill: "r white-black-white-black" }, { type: "ellipse", cx: 150,cy: 150,rx: 40, ry: 30, fill: "r black:25%-white:75%" } ]); });
図形の表示に関わる操作
基本図形には色や見た目の他に,描画に関わる様々な機能が提供されている.
Element.toFront
前面に移動
Element.toBack
背面に移動
Element.hide
図形要素を隠す
Element.show
図形要素を表示する
Element.glow
図形要素の描画に効果をつける.影のような効果をつけることが出来る.
width 広がりの幅
fill 塗りつぶすかどうか
opacity 透明度
offsetx 横方向のずらす幅
offsety 縦方向のずらす幅
color 色
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas5_1", 200, 200); //本来のグラフィック paper.add([ {type:"circle", cx: 40, cy: 40, r: 30,fill: "red"}, {type:"circle", cx: 50, cy: 50, r: 30,fill: "green"}, {type:"circle", cx: 60, cy: 60, r: 30,fill: "blue"} ]); //先頭の円を前面に,2番目の円を隠す var set = paper.add([ {type:"circle", cx: 140, cy: 40, r: 30,fill: "red"}, {type:"circle", cx: 150, cy: 50, r: 30,fill: "green"}, {type:"circle", cx: 160, cy: 60, r: 30,fill: "blue"} ]); set.items[0].toFront(); set.items[1].hide(); //影を付ける paper.add([ {type:"circle", cx: 40, cy: 140, r: 30,fill: "red"}, {type:"circle", cx: 50, cy: 150, r: 30,fill: "green"}, {type:"circle", cx: 60, cy: 160, r: 30,fill: "blue"} ]).glow({width:5,fill:false,opacity:0.5,offsetx:0,offsety:0,color:"black"}); //影の位置を指定する paper.add([ {type:"circle", cx: 140, cy: 140, r: 30,fill: "red"}, {type:"circle", cx: 150, cy: 150, r: 30,fill: "green"}, {type:"circle", cx: 160, cy: 160, r: 30,fill: "blue"} ]).glow({width:1,fill:true,opacity:0.5,offsetx:10,offsety:-10,color:"purple"}); });
前にも示した通り,Setオブジェクトは個々の図形に対するスタイルの指定を簡略化するための機構であるため,影の処理が図形毎に行われている点に注意する.つまり,Raphaëlにおいてはgrowメソッドを用いて複数の図形を組み合わせたもの全体に影を付けると言ったことができない .影専用の図形を用意して元となる画像の背後に配置すると言った工夫が必要となる.
この他、図形の描画に関わる属性としてblur(ぼかし)とclip-rect(描画範囲の制限)がある.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas5_5", 200, 200); //ぼかし paper.add([ {type:"circle", cx: 40, cy: 40, r: 30,fill: "red"}, {type:"circle", cx: 50, cy: 50, r: 30,fill: "green"}, {type:"circle", cx: 60, cy: 60, r: 30,fill: "blue"} ]).attr({blur:2}); //クリップ paper.add([ {type:"circle", cx: 140, cy: 140, r: 30,fill: "red"}, {type:"circle", cx: 150, cy: 150, r: 30,fill: "green"}, {type:"circle", cx: 160, cy: 160, r: 30,fill: "blue"} ]).attr({"clip-rect":"100,120,100,60"}); paper.rect(100,120,100,60).attr("stroke-dasharray", "- "); });
図形の変形
基本図形に対する変形処理には様々なサポート機能が提供されている.※svgにおけるskew(せん断)に相当する機能は存在しない.
Element.scale
図形要素を拡大・縮小する.scale(sx,sy,cx,cy)※中心は省略可能
Element.rotate
図形要素を回転する.rotate(deg,cx,cy)※中心は省略可能
Element.translate
図形要素を平行移動する.translate(dx,dy)
Element.transform
図形要素を変形する.
t操作 translate:平行移動(t dx,dy)
r操作 rotate:回転(r deg,cx,cy)
s操作 scale:拡大・縮小(s sx,sy,cx,cy)
m操作 matrix:マトリクス変換(m a,b,c,d,e,f)
Raphael.parseTransformString
transform文字列を変形操作の配列に分解する.
transformメソッドは非常に高機能で,キーワード「...」(ピリオド×3)を指定することで変形の追加を行うことが出来る.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas5_2", 200, 200); var set = paper.add([ {type:"circle", cx: 140, cy: 140, r: 30,fill: "red"}, {type:"circle", cx: 150, cy: 150, r: 30,fill: "green"}, {type:"circle", cx: 160, cy: 160, r: 30,fill: "blue"} ]); set.transform("r45,100,100t-100,-50s0.5,1"); set.transform("t25,25...s2,2"); });
transformメソッドの補助を行う機能としてMatrixオブジェクトが提供されている.これは図形の変形を(アフィン)行列で表したものとなっている.詳しくはsvgのtransform属性の項を参照.
Raphael.matrix
Matrixオブジェクトを生成する.matrix(a,b,c,d,e,f)
Element.matrix
現在図形要素に掛けられている変形をMatixオブジェクトで取得する.
Matrix.add
行列の足し算を行う.add(a,b,c,d,e,f)もしくはadd(matrix)
Matrix.clone
行列の複製を行う.clone()
Matrix.invert
逆行列を取得する.invert()
Matrix.rotate
変換行列に回転を掛け合わせる.rotate(a,x,y)
Matrix.scale
変換行列に拡大・縮小を掛け合わせる.scale(x,y,cx,cy)
Matrix.translate
変換行列に移動を掛け合わせる.translate(x,y)
Matrix.split
変換行列を原始変形(translate,scale,shear:せん断,rotate)に分解する{dx,dy,scalex,scaley,shear,rotate,isSimple}.isSimpleはrotate,scale,translateのみで構成されていた場合にtrueを返す.split()
Matrix.toTransformString
変換行列のtransform文字列表現を取得する.toTransformString()
Matrix.x
指定した座標を行列変換した後のx座標を取得する.x(x,y)
Matrix.y
指定した座標を行列変換した後のy座標を取得する.y(x,y)
Matrixに対する計算は複数重ねることができるが,その際の変形処理の順は後に行った変形関数から順に実行される† .例えばtranslate→scale→rotateの順にメソッドを実行したのであれば,実際の変形は見た目上rotate→scale→translateの順に行われる.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas5_3", 200, 200); var set = paper.add([ {type:"rect", x: 50, y: 50, width: 80, height: 80,fill: "red"}, {type:"rect", x: 60, y: 60, width: 80, height: 80,fill: "green"}, {type:"rect", x: 70, y: 70, width: 80, height: 80,fill: "blue"} ]); var matrix = Raphael.matrix(); //以下の処理はtransform文字列として「t-50,0s1.5,0.7,r45,100,100」に相当する. matrix.translate(-50,0); matrix.scale(1.5,0.7); matrix.rotate(45, 100, 100); set.transform(matrix.toTransformString()); });
†ここでは図形から見た変形の視点で説明した.transform属性の項でも述べたが,座標軸の変換からも説明できる.この視点で説明するならメソッドを実行した順に座標軸が変換されていることになる.
パスの描画
パスを描画する場合,svgでのd要素で定義されている操作をそのまま利用することが出来る.また,新たにRコマンドが追加されている.このコマンドを用いるとベジェ曲線と異なり,制御点を指定することなしに自動的にカーブを計算し,滑らかに座標間をつなぐことが出来る.
R,r
Catmull-Rom curve.指定した座標を滑らかにつなぐ.
但し,R操作を連続して記述する場合は,間のRの指定は省略しないとエラーとなる.
その他
→Path要素におけるd操作を参照.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas6", 200, 200); //このRコマンドはsvg2.0で追加される可能性があるそうです. paper.add([ { type:"path", //この部分をM 0,0 R 50,150 R 150,50 R 200,200とすることは出来ない. path:"M 0,0 R 50,150 150,50 200,200" }, { type:"path", path:"M 0,200 r 50,-150 150,-50 200,-200" } ]); });
Raphaëlでの塗りつぶしは自動的にくり貫き処理が行われる.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas6_2", 200, 200); paper.add([ { type:"path", path:"M 0,0 R 50,150 150,50 200,200 z M 0,200 r 50,-150 150,-50 200,-200 z", fill:"red", "stroke-width":0 } ]); });
線の描画の調整
pathに限らずstroke-widthの値が奇数だった場合,レンダリング結果の境界部が汚くなるケースがあり,グラフの描画などでくっきりとした表示をしたい場合に問題となる.これは,2つのピクセルにまたがって線が描画されているためであり,これを回避するには描画位置を0.5px(viewBoxの値によって変化する)ずらすとよい.図形の描画位置を直接弄ってもよいが,viewBoxの描画位置をずらす手もある.但し,描画位置を変更することで新たな問題を引き起こす場合があり,根本的な解決策ではない点に注意する.
ieを除外する結果となるが,グラフィックがsvgであることを確認した上で,属性shape-renderingにcrispEdgesを設定する方法もある.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas6_1", 200, 200); paper.add([ {type:"path",path:"M 20,50 h 160"}, {type:"path",path:"M 20,100.5 h 160"} ]).attr({stroke:"blue", "stroke-width":"7"}); //only SVG if(Raphael.svg){ paper.add([ {type:"path",path:"M 20,150 h 160",stroke:"blue", "stroke-width":"7"} ]).items[0].node .style.setProperty("shape-rendering", "crispEdges"); } });
線の装飾
図形要素のstroke-dasharray属性によりstrokeを破線とすることが出来る.なおRaphaël独自記法となっており,指定可能な値は文字「-(ハイフン)」「.(ピリオド)」「_(スペース)」の組み合わせからなる- . -. -.. ._ -_ -- -_. --. --.. の10種類のみで,それ以外は無視される.
Created with Raphaël 2.1.0 無し 1:- 2:. 3:-. 4:-.. 5:._ 6:-_ 7:-- 8:-_. 9:--. 10:--..
Raphael(function(){ var paper = Raphael("canvas7_0", 200, 200); paper.add([ {type: "path",path: "M 40,20 h 140","stroke-dasharray" :""}, {type: "path",path: "M 40,35 h 140","stroke-dasharray" :"-"}, {type: "path",path: "M 40,50 h 140","stroke-dasharray" :"."}, {type: "path",path: "M 40,65 h 140","stroke-dasharray" :"-."}, {type: "path",path: "M 40,80 h 140","stroke-dasharray" :"-.."}, {type: "path",path: "M 40,95 h 140","stroke-dasharray" :". "}, {type: "path",path: "M 40,110 h 140","stroke-dasharray" :"- "}, {type: "path",path: "M 40,125 h 140","stroke-dasharray" :"--"}, {type: "path",path: "M 40,140 h 140","stroke-dasharray" :"- ."}, {type: "path",path: "M 40,155 h 140","stroke-dasharray" :"--."}, {type: "path",path: "M 40,170 h 140","stroke-dasharray" :"--.."} ]).attr({"stroke-width": 3}); paper.add([ {type:"text", text:"無し", y:20}, {type:"text", text:"1:-", y:35}, {type:"text", text:"2:.", y:50}, {type:"text", text:"3:-.", y:65}, {type:"text", text:"4:-..", y:80}, {type:"text", text:"5:._", y:95}, {type:"text", text:"6:-_", y:110}, {type:"text", text:"7:--", y:125}, {type:"text", text:"8:-_.", y:140}, {type:"text", text:"9:--.", y:155}, {type:"text", text:"10:--..", y:170} ]).attr({x:5, "font-size":13, "text-anchor":"start"}); });
stroke-linecapと合わせて丸い破線とすることも出来る.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas7_1", 200, 200); paper.add([ {type: "path",path: "M 20,30 h 160","stroke-dasharray" :""}, {type: "path",path: "M 20,50 h 160","stroke-dasharray" :"-"}, {type: "path",path: "M 20,70 h 160","stroke-dasharray" :"."}, {type: "rect",x: 20, y: 100, width: 160, height: 40, "stroke-dasharray" :"."} ]).attr({"stroke-width": 5, "stroke-linecap":"round","stroke-linejoin": "round"}); });
マーカーの指定
パスではarrow-start,arrow-end属性を用いることで簡単にマーカーを設定することが出来る.マーカーの色はstrokeの値を継承する.マーカーが設定される場所は必ず端点であり,パスの頂点にマーカーを設定することは出来ない.
記述法:[スタイル]-[幅(narrow/midium/wide)]-[長さ(short/midium/long)]
classic
矢じり型
block
三角形型
open
鋭角型
oval
楕円型
diamond
菱形
none
無し
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvas7", 200, 200); paper.add([ {type: "path",path: "M 20,20 h 160", "arrow-start" :"classic-wide-long", "arrow-end" :"classic-narrow-long"}, {type: "path",path: "M 20,40 h 160", "arrow-start" :"block-wide-long", "arrow-end" :"block-narrow-long"}, {type: "path",path: "M 20,60 h 160", "arrow-start" :"open-wide-long", "arrow-end" :"open-narrow-long"}, {type: "path",path: "M 20,80 h 160", "arrow-start" :"oval-wide-long", "arrow-end" :"oval-narrow-long"}, {type: "path",path: "M 20,100 h 160", "arrow-start" :"diamond-wide-long", "arrow-end" :"diamond-narrow-long"}, {type: "path",path: "M 20,120 h 160", "arrow-start" :"none-wide-long"}, {type: "path",path: "M 20,140 l 40,40", "arrow-end" :"classic-narrow-short"}, {type: "path",path: "M 50,140 l 40,40", "arrow-end" :"classic-midium-midium"}, {type: "path",path: "M 80,140 l 40,40", "arrow-end" :"classic-wide-long"}, {type: "path",path: "M 110,140 l 15,15 m 5,5 l 20,20", "arrow-end" :"classic-wide-long"} ]).attr({"stroke-width": 3, stroke: "red"}); });
パスの操作に関わるメソッド群
pathオブジェクトにはパスの長さや部分パスを取り出すためのメソッドが定義されている.
Element.getPointAtLength
与えた長さにおける座標を返す{x,y}.getPointAtLength(length)
Element.getSubpath
与えた長さに対するサブパス文字列を返す.この文字列を元にpathを描くことが出来る.getSubpath(from,to)
Element.getTotalLength
パスの全長を返す.getTotalLength()
Created with Raphaël 2.1.0 M,10,140,C,10,140,100,100,100,100,C,100,100,190,140,190,140,C,190,140,100,180,100,180,C,100,180,10,140,10,140
Raphael(function(){ var paper = Raphael("canvasP", 200, 200); var path = paper.path("M 10,10 Q 100,10 180,50"); var point = path.getPointAtLength(30); paper.circle(point.x,point.y,5).attr("fill", "red"); var sub = path.getSubpath(50,120); paper.path(sub).attr({stroke: "blue", "stroke-width": 5 }); // var p = "M 10,80 Q 100,60 190,80"; paper.path(p); var matrix = Raphael.matrix(); matrix.rotate(10,100,80); paper.path(Raphael.mapPath(p,matrix)).attr("stroke", "green"); // var p2 = "M10,140 L 100,100 L 190,140 L 100,180 z" var ps = Raphael.path2curve(p2); paper.path(ps.join()).attr("stroke", "blue"); paper.text(100,160,ps.join()); });
パスに関わるユーティリティメソッド群
この他にもRaphaëlではパスを操作する上で便利な機能を多数提供している.パスの交点を算出するなど,単純ではあるが面倒な処理も簡単に実現することが出来る.Raphaelオブジェクトのもつ関数は図形要素を生成しないため,算出したパス情報をSVGPathElementに適用することも可能 だ.
Raphael.getPointAtLength
パス文字列の与えた長さにおける座標を返す{x,y}.getPointAtLength(path, length)
Raphael.getSubpath
パス文字列の与えた長さに対するサブパス文字列を返す.getSubpath(path, from, to)
Raphael.getTotalLength
パス文字列に対するパスの全長を返す.getTotalLength()
Raphael.parsePathString
パスを線分の配列とする.parsePathString(path)
Raphael.path2curve
パスの線分を見た目を変えず(直線を含めて)全てc操作の配列に変換する.パスの記述形式を揃えることでモーフィング処理が容易に行えるようになる.path2curve(path)
Raphael.pathIntersection
パスの交わる部分を取得する.交点の座標,交点の位置(0〜1),交差した線分のインデックス,交差した線分の3次ベジェ曲線の頂点値.pathIntersection(path1,path2)
Raphael.pathToRelative
パスの表記を相対座標指定とする.こうすることで先頭の座標を変更するだけでパス全体を移動することが可能となる.
Raphael.mapPath
パスを行列で変形し,その結果をpath文字列として取得する.mapPath(path, matrix)
Raphael.transformPath
パスをtransform文字列で変形し,その結果をpath文字列で取得する.transformPath(path,transformString)
Raphael.toMatrix
パスオブジェクトが描画されている座標系への変換Matrixを取得する.同じ座標系に図形を描画したい場合に利用する.toMatrix(path,transformString)
Raphael.findDotsAtSegment
3次ベジェ曲線を構成する4点(8引数)と曲線の分割位置を与え,新たな制御点を得る.得られる制御点はstart,m,n,endの4つ.
例を示す.元の曲線を"M a,b C c,d e,f g,h"とし,分割点をtとすると,得られる曲線の分割は”M a,b C start.x,start.y m.x,m.y t.x,t.y C n.x,n.y end.x,end.y g,h”となる.分割後も,path全体としては全く同じ形を保つ .
パスの交点を取得する
pathIntersectionメソッドを利用すると,二つのパスの交点の座標を取得することが出来る.戻り値は交点情報の配列となっており,パスの交差回数によって長さが変化する.交点情報オブジェクトには交点の座標を表すx,yプロパティの他,交差したパス切片に相当するbez1,bez2プロパティが含まれている.このプロパティにはCコマンドに相当する座標情報が格納されている.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasP2", 200, 200); var path1 = "M 10,10 H 30 Q 200,30 150,80 L 80,180 H 10"; paper.path(path1); var path2 = "M 190,10 h -40 Q 30,50 30,80 L 190,180"; paper.path(path2); //pathの交点情報を取得する var inter = Raphael.pathIntersection(path1, path2); //今回は2点で交差するため,interの長さは2となる. //bez1は交点が存在するpath1での線分.bez2は交点が存在するpath2での線分. //下のようにすることでCコマンドに変換することが出来る. paper.add([ {type: "path", path:Raphael.format("M{0},{1}C{2},{3} {4},{5} {6},{7}", inter[0].bez1)}, {type: "path", path:Raphael.format("M{0},{1}C{2},{3} {4},{5} {6},{7}", inter[0].bez2)}, ]).attr({"stroke-width": 3, stroke: "blue"}); paper.add([ {type: "path", path:Raphael.format("M{0},{1}C{2},{3} {4},{5} {6},{7}", inter[1].bez1)}, {type: "path", path:Raphael.format("M{0},{1}C{2},{3} {4},{5} {6},{7}", inter[1].bez2)}, ]).attr({"stroke-width": 3, stroke: "green"}); //交点座標 paper.add([ {type: "circle", cx:inter[0].x ,cy:inter[0].y ,r:5 }, {type: "circle", cx:inter[1].x ,cy:inter[1].y ,r:5 } ]).attr({"stroke-width": 3, stroke: "red"}); });
ベジェ曲線の分割
ベジェ曲線は任意の位置で二つのベジェ曲線に分割できることが数学的に確かめられており,findDotsAtSegmentメソッドを用いることでこの計算を自動的に行うことが出来る.得られたオブジェクトには分割位置に対する座標(x,y)の他に分割後のベジェ曲線の制御点に相当するstart,m,n,endの4座標の情報が含まれている.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasP3", 200, 200); //ベジェ曲線の分割 var p2 = "M 10,10 C 400,30 -110,180 190,190" paper.path(p2); var part = Raphael.findDotsAtSegment(10,10,400,30,-110,180,190,190, 0.4); //分割後のベジェ曲線を表示する paper.add([{ type: "path", path: Raphael.format("M10,10 C{0},{1} {2},{3} {4},{5} C{6},{7} {8},{9} 190,190", part.start.x, part.start.y,//制御点1 part.m.x, part.m.y, //制御点2 part.x, part.y,//分割点 part.n.x, part.n.y, //制御点3 part.end.x, part.end.y)//制御点4 }]).attr({"stroke-width": 3, stroke: "purple", "stroke-dasharray":"- "}); //制御点を表示する paper.add([{ type: "path", path: Raphael.format("M10,10L{0},{1}" + "M{2},{3}L{4},{5}" + "M{4},{5}L{6},{7}" + "M{8},{9}L190,190", part.start.x, part.start.y,//制御点1 part.m.x, part.m.y, //制御点2 part.x, part.y,//分割点 part.n.x, part.n.y, //制御点3 part.end.x, part.end.y)//制御点4 }]).attr({"stroke-width": 1, "stroke-dasharray":"- "}); paper.circle(part.x, part.y, 5).attr({"stroke-width": 3, stroke: "red"}); });
座標の判定処理
Raphaelでは様々な座標に関わる判定メソッドが提供されている.
Raphael.is
データ型の判定を行う.
Element.getBBox
図形要素の描画領域を矩形範囲オブジェクトとして取得する.getBBox(isWithoutTransform)変形を加味するか
Raphael.bezierBBox
ベジェ曲線の描画範囲を矩形範囲オブジェクトとして算出する.bezierBBox()
Raphael.pathBBox
パスに対する描画範囲を矩形範囲オブジェクトとして取得する.pathBBox()
Raphael.isBBoxIntersect
矩形範囲オブジェクトが共通部分をもつかどうか.isBBoxIntersect(bbox1, bbox2)
Raphael.isPointInsideBBox
矩形範囲オブジェクトが座標を含むかどうか.isPointInsideBBox(bbox, x,y)
Raphael.isPointInsidePath
パス文字列が指定した座標を含むかどうか.isPointInsidePath(pathString, x,y)
Element.isPointInside
指定した座標が図形要素に含まれるかどうかを取得する.isPointInside(x,y)
Paper.getElementByPoint
指定した座標の最も上にある図形オブジェクトを取得する.getElementByPoint(x,y)
※現在仕様を含めたバグが存在しており,期待通りの動きとならない.
Paper.getElementsByPoint
指定した座標の図形オブジェクトのSetを取得する.getElementsByPoint(x,y)
ここで矩形範囲オブジェクトはプロパティにx,y,x1,x2,width,heightをもつもので,Element.getBBoxで得られるオブジェクトはこの条件に適合する.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasC", 200, 200); //Element.getBBox var rect = paper.rect(30,30,50,50); rect.attr("fill", "blue").transform("r45,55,55"); var bbox1 = rect.getBBox(false); var bbox2 = rect.getBBox(true); paper.add([ {type:"rect" ,x: bbox1.x, y: bbox1.y, width: bbox1.width, height:bbox1.height}, {type:"rect" ,x: bbox2.x, y: bbox2.y, width: bbox2.width, height:bbox2.height} ]).attr("stroke-dasharray", "- "); //Raphael.bezierBBox var bbbox = Raphael.bezierBBox(100,10,150,10,190,70,190,100); paper.path("M 100,10 C150,10 190,70 190,100"); paper.rect(bbbox.x, bbbox.y, bbbox.width, bbbox.height).attr("stroke-dasharray", "- "); //Raphael.pathBBox var path = "M 50,150 L 10,110 L 90,150 L 10,190 z"; var bbbox = Raphael.pathBBox(path); paper.path(path).attr("fill","red"); paper.rect(bbbox.x, bbbox.y, bbbox.width, bbbox.height).attr("stroke-dasharray", "- "); //Raphael.isBBoxIntersect var r1 = paper.rect(110,110,30,30); var r2 = paper.rect(120,120,30,30); var r3 = paper.rect(155,150,19,19); var r4 = paper.rect(175,170,19,19); if(Raphael.isBBoxIntersect(r1.getBBox(false),r2.getBBox(false))){ r1.attr("fill","red"); r2.attr("fill","red"); } if(Raphael.isBBoxIntersect(r3.getBBox(false),r4.getBBox(false))){ r3.attr("fill","red"); r4.attr("fill","red"); } });
矩形範囲だけでなく,図形の描画範囲による座標の包含を判定することも可能.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasC1", 200, 200); //Raphael.isPointInsideBBox var r1 = paper.rect(10,10,80,40); var r2 = paper.rect(10,50,80,40); if(Raphael.isPointInsideBBox(r1.getBBox(false), 20,20)){ r1.attr("fill","red"); } if(Raphael.isPointInsideBBox(r2.getBBox(false), 20,20)){ r2.attr("fill","red"); } paper.circle(20,20,3).attr("fill", "black"); //Raphael.isPointInsidePath var p1 = "M 100,50 Q 150,-20 200,50"; var p2 = "M 100,100 Q 150,30 200,100"; var path1 = paper.path(p1); var path2 = paper.path(p2); if(Raphael.isPointInsidePath(p1, 160,45)){ path1.attr("fill","red"); } if(Raphael.isPointInsidePath(p2, 160,45)){ path2.attr("fill","red"); } //Element.isPointInside paper.circle(160,45,3).attr("fill", "black"); var c1 = paper.circle(50,150,40); var c2 = paper.circle(100,150,40); if(c1.isPointInside(60,130)){ c1.attr("fill","red"); } if(c2.isPointInside(60,130)){ c2.attr("fill","red"); } paper.circle(60,130,3).attr("fill", "black"); });
座標を指定して,図形要素を串刺しにすることもできる.※getElementByPointメソッドは現在正しく動作しない .マウスカーソルによる図形の選択を意識しているようだが,中々意図した通りの動作とならないため,getElementsByPointメソッドを使い,最後の図形要素を取得するとよい.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasC2", 200, 200); //Paper.getElementByPoint,Paper.getElementsByPoint for(var i = 0; i<11; i++){ paper.circle(50 + i * 10, 50 + i * 10, 40); } //現状ページを基準とした座標が有効となってしまっているらしい. //paper.getElementByPoint(175,175).attr("fill", "blue"); paper.getElementsByPoint(80,80).attr("fill", "green"); paper.circle(80,80,3).attr("fill", "black"); });
アニメーション処理
アニメーション機構を用いることで,cssにおけるtransitionやanimationのような効果を得ることができる.機能が絞られている分習得はしやすいものの,複雑な動きを実現することはできない.とは言え,面倒ではあるが独自のスクリプトを組むことでいくらでも機能を充実することができるので,それほど気にはならない.
アニメーションを実行するにはElement.animateメソッドを呼び出せば良い.現在の設定値からメソッドに指定した値まで徐々に値を変化させる ことでアニメーションを表現する.イベント処理と組み合わせれば,任意のタイミングでアニメーションを開始できる.また,setTimeout関数と組み合わせれば,繰り返し処理とすることもできる.なお,animateメソッドでアニメーション化出来るプロパティは次の通り.また色のアニメーションは可能だが,グラデーションのアニメーションまでは対応していない.
アニメーション化可能な属性
blur,clip-rect,cx,cy,fill,fill-opacity,font-size,height,opacity,path, r,rx,ry,stroke,stroke-opacity,stroke-width,transform,width,x,y
Raphael.animation
Animationオブジェクトを生成する.animation({params},ms,easing,callback)
Animation.delay
指定した遅延時間分,実行開始を指定のミリ秒だけ遅らせる.repeatが設定されていた場合は,そのループ毎に遅延が発生する .
Animateion.repeat
指定した回数分繰り返すAnimationオブジェクトを返す.無制限は文字列"Infinity"を指定する.
Element.animate
アニメーションを行う.animate({params},ms,easing,callback)もしくはanimation(animation)
Element.animateWith
アニメーションを経過時間を同期する.animateWith(targetElem,targetAnim,ms,easing,callback)もしくはanimateWith(targetElem,targetAnim,animation)
Animationオブジェクトはアニメーション動作の定義を行うもので,Element.animateメソッドの引数に利用する.複数の図形要素のアニメーション処理に使いまわすことが出来る.また,拡張機能としてディレイ効果や,繰り返し設定を簡単に実現することができる.Raphaël内部ではこのAnimationとElementの組みでアニメーションが管理されており,アニメーションを制御する場合にAnimationオブジェクトが必要となる.
Element.animateメソッドは重ねがけすることが出来る.そのため異なる属性に別のアニメーションを施すことで表現の幅が広がる.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasAn", 200, 200); //最も簡単なアニメーション //jQueryの記法と同じなので,馴染みのある書き方だろう. var circle = paper.circle(10,10,10).attr("fill","red"); circle.click(function(){ circle.animate({cx:190},6000,"linear", function(){this.attr({cx:10});}); }); //素朴にアニメーションを繰り返す例 var circle2 = paper.circle(10,30,10).attr("fill","red"); repeat(); function repeat(){ circle2.attr({cx:10}); circle2.animate({cx:190},6000,"linear", function(){ //setTimeout関数を使ってアニメーション処理を繰り返す setTimeout(repeat,0); }); } //Animationオブジェクトを使って繰り返す var circle3 = paper.circle(10,70,10).attr("fill","red"); var anim = Raphael.animation({cx:190},6000,"linear"); circle3.animate(anim.repeat("Infinity")); //Animationオブジェクトを使って記述を簡略化する var circle4 = paper.circle(10,110,10).attr("fill","red"); var circle5 = paper.circle(10,130,10).attr("fill","red"); var circle6 = paper.circle(10,150,10).attr("fill","red"); var anim2 = Raphael.animation({cx:190},6000,"linear"); circle4.animate(anim.repeat("Infinity")); circle5.animate(anim.repeat("Infinity").delay(100)); circle6.animate(anim.repeat("Infinity").delay(200)); //animateメソッドを重ねがけしてみよう var circle7 = paper.circle(10,170,10).attr("fill","blue"); var anim1 = Raphael.animation({cx:190},6000,"linear").repeat("Infinity"); var anim2 = Raphael.animation({cy:190},1000,"linear").repeat("Infinity"); repeat2(); function repeat2(){ circle7.attr({cx:10,cy:190}); circle7.animate({cx:190},3000,"linear") .animate({cy:170},6000,"linear", function(){ //setTimeout関数を使ってアニメーション処理を繰り返す setTimeout(repeat2,0); }); } //Animationオブジェクトを使っても同様に行える. var circle8 = paper.circle(10,170,10).attr("fill","red"); var anim3 = Raphael.animation({cx:190},6000,"linear").repeat("Infinity"); var anim4 = Raphael.animation({cy:190},1000,"linear").repeat("Infinity"); circle8.animate(anim3).animate(anim4); });
Element.animateWithメソッドを用いると,現在動作しているアニメーションとこれから実行しようとするアニメーションの経過時間を合わせることが出来る.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasAn1", 200, 200); var circle1 = paper.circle(10,10,10).attr("fill","red"); var anim1 = Raphael.animation({cx:190},6000,"linear", function(){this.attr({cx:10});} ); circle1.click(function(){ circle1.animate(anim1); }); var circle2 = paper.circle(10,30,10).attr("fill","red"); var anim2 = Raphael.animation({cx:190}, 6000, "linear", function(){this.attr({cx:10});} ); circle2.click(function(){ circle2.animateWith(circle1, anim1, anim2); }); });
パスの変形アニメーション
Raphaëlが提供するアニメーション処理の真骨頂は,異なる形状のpathを滑らかに変形できるところだろう.強力なpathの変形機能を利用して円を矩形に変形することも可能だ.pathが複数のパーツに分かれていてもアニメーション化可能だが,変形処理自体を制御することができないため,イメージ通りに動かすのは非常に困難である.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasAn3", 200, 200); var path1 = "M 25,100 A 75,75 0 1,1 175,100 A 75,75 0 1,1 25,100 Z"; var path2 = "M 150,150 h -100 v -100 h 100 z"; var path = paper.path(path1).attr({fill: "blue", "stroke-width":0}); var anim = Raphael.animation({path:path2, fill: "red"}, 12000, "elastic").delay(2000).repeat("Infinity"); path.animate(anim); });
transform属性に対するアニメーション
またアニメーションをtransform属性に掛けることも可能だ.インターフェースが統一されているため,判りやすい.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasAn4", 200, 200); var path1 = "M 50,0 a 50,50 0 1,1 0,100 a 50,50 0 1,1 0,-100 z M 150,100 a 50,50 0 1,1 0,100 a 50,50 0 1,1 0,-100 z"; var path2 = "M 75,175 h -50 v -50 h 50 z M 175,75 h -50 v -50 h 50 z M 75,75 h -50 v -50 h 50 z M 175,175 h -50 v -50 h 50 z"; var path = paper.path(path1).attr({fill: "blue", "stroke-width":0}); var anim = Raphael.animation({path:path2, fill: "red",transform:"r360,100,100"}, 12000, "bounce").delay(2000).repeat("Infinity"); path.animate(anim); });
イージング関数の種類
アニメーションを行う際のイージング関数は次のものが提供されている.
linear
線形.
easeIn
加速「<」「easeIn」「ease-in」
easeOut
減速「>」「easeOut」「ease-out」
easeInOut
加減速「>」「easeInOut」「ease-in-out」
backIn
後ろに勢いを付けて.「backIn」「back-in」
backOut
終了点で少しはみ出す.「backOut」「back-out」
elastic
振動.
bounce
バウンド効果.
cubic-bezier
3次ベジェ曲線による.「cubic-bezier(x1, y1, x2, y2)」
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasAn2", 200, 200); Raphael.getColor.reset(); paper.circle(20,20,10).attr("fill",Raphael.getColor(1)).click(function(){ this.animate({cx:180},2000,"linear", function(){this.attr({cx:20})});}); paper.circle(20,40,10).attr("fill",Raphael.getColor()).click(function(){ this.animate({cx:180},2000,"easeIn", function(){this.attr({cx:20})});}); paper.circle(20,60,10).attr("fill",Raphael.getColor()).click(function(){ this.animate({cx:180},2000,"easeOut", function(){this.attr({cx:20})});}); paper.circle(20,80,10).attr("fill",Raphael.getColor()).click(function(){ this.animate({cx:180},2000,"easeInOut", function(){this.attr({cx:20})});}); paper.circle(20,100,10).attr("fill",Raphael.getColor()).click(function(){ this.animate({cx:180},2000,"backIn", function(){this.attr({cx:20})});}); paper.circle(20,120,10).attr("fill",Raphael.getColor()).click(function(){ this.animate({cx:180},2000,"backOut", function(){this.attr({cx:20})});}); paper.circle(20,140,10).attr("fill",Raphael.getColor()).click(function(){ this.animate({cx:180},2000,"elastic", function(){this.attr({cx:20})});}); paper.circle(20,160,10).attr("fill",Raphael.getColor()).click(function(){ this.animate({cx:180},2000,"bounce", function(){this.attr({cx:20})});}); paper.circle(20,180,10).attr("fill",Raphael.getColor()).click(function(){ this.animate({cx:180},2000,"cubic-bezier(1,0,0,1)", function(){this.attr({cx:20})});}); });
アニメーションの制御
アニメーションの制御を行うメソッドとしては次が提供されている.しかし,現状動作が不安定なようで,当方の環境では思うような制御が行えなかった.なお,内部エラーが発生するとページ全体のアニメーションが停止してしまう.どうやらアニメーションの管理は一箇所で行なっているようだ.
Element.setTime
アニメーションの経過時間を設定する.setTime(animation,time)
Element.stop
アニメーションを停止する.stop(),stop(animation)
Element.pause
アニメーションを一時停止する.pause(),pause(animation)
Element.resume
アニメーションを再開する.resume(),resume(animation)
Element.status
アニメーションの状態(0から1のアニメーション実行経過の割合)を取得・設定する.内部で動作しているAnimateオブジェクトを取得する目的でも使える.status()…[{animate,status}],status(animate)…status,status(animate,status)…値の設定
補足)animateAlongメソッドについて
文献によっては図形をpathに沿って動かすanimateAlongメソッドが存在しているが,これはRaphaëlのバージョンアップに伴い削除された .これと同等の機能を実現する場合は,パスの全長をgetTotalLengthメソッドで取得し,getPointAtLengthメソッドで道のりに対する座標を算出して図形の描画基準点を決定すると良い.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasAA", 200, 200); var path = paper.path("M 10,10 R 100,150 190,100 100,50 10,190") .attr({stroke: "blue", "stroke-dasharray": "- "}); var length = path.getTotalLength(); var circle = paper.circle(0,0,5).attr({fill: "red",stroke: "none"}); var i = 0; setInterval(function(){ var point = path.getPointAtLength(length/120 * (i++%120)); circle.attr("cx", point.x); circle.attr("cy", point.y); },50); });
リンクの設定
Element.attrメソッドを使ってハイパーリンクを定義することが出来る.href属性でリンク先を,target属性でリンクの表示先ウインドウを指定する.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasL", 200, 200); var circle = paper.circle(100,100,80) .attr({fill:"red",stroke:"orange" , "stroke-width":10, href: "#", target:"blank"}) .hover(function(){this.attr("fill","yellow")} ,function(){this.attr("fill","red")}); });
ユーザーイベント
以下にイベント処理に関わるメソッドを示す.複数のイベントハンドラをまとめて設定可能なhoverやdrag等,便利なものが提供されている.イベントハンドラを登録するメソッドには必ずハンドラを解除するメソッドが提供されている.touch系のイベントはAppleが提唱したごく最近定められた もので,利用できる環境が限られる.
イベントハンドラ内部でのthis変数にはイベントを発生させた図形オブジェクトが設定される.簡単な例を示す.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasR1", 200, 200); var circle = paper.circle(100,100,80) .attr({fill:"red",stroke:"orange" ,"stroke-width":10}); circle.click(function(e){ this.attr("fill", Raphael.getColor(1)); }); });
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasR2", 200, 200); var circle = paper.circle(50,50,40) .attr({fill:"red",stroke:"orange" ,"stroke-width":5}); circle.drag(function(dx,dy){ this.attr("cx", this._cx + dx).attr("cy", this._cy + dy); },function(){ this._cx = this.attr("cx"); this._cy = this.attr("cy"); },function(){ delete(this._cx); delete(this._cy); }); });
Element.click
図形要素をクリックした際の処理を設定する.
Element.dblclick
図形要素をダブルクリックした際の処理を設定する.
Element.drag
図形要素をドラッグした際の処理を設定する.drag(onmove, onstart, onend, [mcontext], [scontext], [econtext])
Element.hover
図形要素にカーソルが当たった際の処理を設定する.mouseover,mouseoutイベントを一括して登録することが出来る.hover(f_in, f_out, [in_context], [o_context])
Element.mousedown
図形要素においてマウスボタンを押下した際の処理を設定する.
Element.mousemove
図形要素においてカーソルを動かした際の処理を設定する.
Element.mouseout
図形要素においてカーソルが範囲外に出た際の処理を設定する.
Element.mouseover
図形要素においてカーソルが上に乗った際の処理を設定する.
Element.mouseup
図形要素においてマウスボタンを離した際の処理を設定する.
Element.touchcancel
図形要素においてタッチがキャンセルされた際の処理を設定する.
Element.touchend
図形要素においてタッチが終了された際の処理を設定する.
Element.touchmove
図形要素においてタッチの移動がなされた際の処理を設定する.
Element.touchstart
図形要素においてタッチが開始された際の処理を設定する.
Element.unXXX
イベント処理の登録を解除する.unclick等.
ドラッグ操作の拡張
ドラッグ操作による処理は,onDragOverメソッドと組み合わせることで応用範囲が更に広がる .
Element.onDragOver
ドラッグオーバー時の処理を設定する.関数の引数にはカーソル直下の要素が渡される.引数無指定でイベントハンドラのバインドを解除する.内部ではdrag.over.[id]というカスタムイベントが実行されている.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasDrgover", 200, 200); paper.setStart(); for(var j=0; j<40; j++){ for(var i=0; i<40; i++){ paper.rect(i*5,j*5,5,5); } }; paper.setFinish().attr({ fill:"white", stroke:"none", "stroke-width":0 }); var circle = paper.circle(25,25,12.5) .attr({fill:"red",stroke:"orange" ,"stroke-width":2.5}); circle.drag( function(dx,dy){ this.attr("cx", this._cx + dx).attr("cy", this._cy + dy); },function(){ this._cx = this.attr("cx"); this._cy = this.attr("cy"); },function(){ delete(this._cx); delete(this._cy); } ).onDragOver(function(elem){ elem.attr("fill", "blue"); }); });
eveによるカスタムイベントの実行とアニメーションイベント
先に示したものはdomが発生するイベントだが,Raphaëlには同作者によるjavascriptライブラリeve が組み込まれており,カスタムイベントの定義・実行が可能である.
カスタムイベントは,「/」「.」で区切られた文字列で管理されている.例えばeve.onメソッドでイベント「a.b.c」を発生させると,階層順に「a」→「a.b」→「a.b.c」の順番でそれぞれ登録されている関数が実行される.また,ワイルドカード「*」を使って関数を登録することも可能である.ワイルドカードの方が先に実行されるが,詳細はサンプルを参照のこと.
Created with Raphaël 2.1.0 (1)ABCDEFG (2)ABH (3)ABCDF (4)ABH
Raphael(function(){ var paper = Raphael("canvasEv", 200, 200); var tmp; //*を含んだイベント名称の実行順を調べる eve.on("a", function(){tmp = "A";}); eve.on("a.*", function(){tmp += "B";}); eve.on("a.b", function(){tmp += "C";}); eve.on("a.*.*", function(){tmp += "D";}); eve.on("a.*.c", function(){tmp += "E";}); eve.on("a.b.*", function(){tmp += "F";}); eve.on("a.b.c", function(){tmp += "G";}); eve.on("a.d", function(){tmp += "H";eve.stop();});//サブイベントを中断 eve.on("a.d.e", function(){tmp += "I";}); //eveメソッドを使ってイベントを着火 paper.setStart(); eve("a.b.c"); paper.text(20,20,"(1)"+tmp); eve("a.d.c"); paper.text(20,40,"(2)"+tmp); eve("a.b.d"); paper.text(20,60,"(3)"+tmp); eve("a.d.e"); paper.text(20,80,"(4)"+tmp); paper.setFinish().attr({"font-size":20, "text-anchor": "start"}); });
eve
カスタムイベントを実行する.eve(name,scope,varargs)scope…実行コンテクスト,varargs…イベントハンドラーに渡されるパラメータ
eve.listeners
イベントハンドラ関数の配列を取得する.listeners(name)
eve.nt
現在実行しているイベントの名称を取得する.イベントハンドラ関数内部で実行する.nt(subname)…指定した名称を含んでいるか.nt()現在のイベント名称を取得する.
eve.on
イベントハンドラを定義する.on(name,f)
eve.once
イベントハンドラを定義する.一度実行されると,自動的に削除される.onece(name,f)
eve.stop
これ以降のサブイベントの着火を停止する.stop()
eve.unbind
イベントハンドラを削除する.unbind(name,f)
eve.version
組み込まれているeveのバージョン.
アニメーションイベント
また,この機能を用いてアニメーション開始・実行・終了時のイベント処理を行える.
raphael.anim.start.[id]
アニメーション開始時に実行される処理の識別子.
raphael.anim.frame.[id]
アニメーション中に実行される処理の識別子.
raphael.anim.finish.[id]
アニメーション終了時に実行される処理の識別子.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("canvasAnA", 200, 200); var path1 = paper.path("M 0,0 h 200").attr("stroke", "blue"); var anim1 = Raphael.animation({path:"M 0,200 h 200"}, 12000, "linear").repeat("Infinity"); path1.animate(anim1); var path2 = paper.path("M 0,0 v 200").attr("stroke", "green"); var anim2 = Raphael.animation({path:"M 200,0 v 200"}, 17000, "linear").repeat("Infinity"); path2.animate(anim2); var circle = paper.circle(0,0,5).attr("stroke", "red"); //frameイベントで円の中心を直線の交点を合わせる. eve.on("raphael.anim.frame." + path1.id,function(){ var point = Raphael.pathIntersection(path1.attr("path"), path2.attr("path"))[0]; circle.attr({cx:point.x, cy:point.y}); }); });
テキストとフォント
テキストを描画するにはPaper.textメソッドを用いる.このメソッドでは文字列の内容が内部に保持されているため,後から自由に中身を書き換えることが出来る.憶えておくべき点は次の通りである.
テキストの中身を書き換えるには,text属性を編集すれば良い.
改行を挿入する場合は改行部に「\n」を記述する.それ以外のエスケープシーケンスは無視される.
利用できる属性にはtext-anchor,font,font-family,font-size,font-weight,font-styleがある.
Raphaëlではもうひとつ文字列を描画する方法として,Paper.printメソッドが提供されている.これはCufón 形式のデータを読み込み,フォントとして利用するものだ.Cufónはttfやotf形式のフォントを独自形式のjsonデータに変換し ,html文書に表示させるライブラリだ.フォントの外形をpathに変換して利用するため,環境に依存しないフォント描画が期待できる.Paper.printメソッドではテキストの内容をpathに変換しているため,後からテキストの内容を書き換えると言った処理を行うことができない.Paper.printメソッドを用いるには次のようにする.
Raphael.registerFontメソッドを呼び出し,フォントをライブラリに登録する.
Paper.getFontフォントを呼び出し,fontオブジェクトを呼び出す.その際のフォント名称にはフォントデータjsonのfont-family属性の値を指定する.
Paper.printメソッドにてテキストを描画する.
Raphael.registerFont
フォントを登録する.registerFont(cufon形式のフォントデータjson)
Paper.getFont
フォント設定をオブジェクトとして利用する.getFont(フォント登録名,font-weight,font-style,font-stretch)※フォント登録名以下の指定はフォントデータ内に該当するデータが存在しない場合は無効.
Paper.print
文字を描画する.描画の基準は左端からとなる.得られる結果はpath図形オブジェクトとなる.print(x,y,string,font,[size],[origin],[letter_spacing])※originはbaselineかmiddleを指定する.
参考:下の例を実行する前に行なっている処理.(抜粋)
Raphael.registerFont( {w:229,face:{"font-family":"whoa" ,"font-weight":400,"font-stretch":"normal","units-per-em":"360","panose-1":"2 0 5 0 0 0 0 0 0 0",ascent:"288",descent:"-72",bbox:"-0.738298 -325 315.086 19","underline-thickness":"26.3672","underline-position":"-24.9609","unicode-range":"U+0020-U+007A"},glyphs:{" ":{w:105},"\u00a0":{w:105},"!":{d:"87,-270v25,40,-13,187,-18,168v-…
Created with Raphaël 2.1.0 It's Raphael! Very Nice!
Raphael(function(){ //一般的なテキスト利用方法 var paper = Raphael("canvasText", 200, 200); var text = paper.text(100,40,"It's Raphael!").attr("font-size", 20).attr("font-style", "italic"); text.attr("text", text.attr("text") + "\nVery Nice!"); //事前にwhoaフォントを登録しておくことで,fontオブジェクトを呼び出すことができます. //Raphael.registerFont({w:229,face:{"font-family":"whoa",… //フォントは公式サイトのデモから拝借しました. var font = paper.getFont("whoa"); var print = paper.print(0,100, "Use Cufon font!\nIt looks so cool.", font, 20,"baseline",0); print.attr("fill", "90-blue-red"); });
なお,日本語環境ではフォント全体を変換するとデータ量が膨大となってしまうので,使いたい文字のみを抽出するようにしたい.
要素ツリーの構造に関わるメソッド群
ベクタグラフィックは内部で(フラットな)階層構造をとるが,要素の順番は図形の重なり順に相当 する.先に見た,Element.toFront/toBackはこの順番を入れ替えることで図形の上下関係を変更している.この他図形要素の操作を行うメソッドとしては次のようなものが提供されている.
Element.clone
図形要素を複製する.
Element.id
図形要素の内部識別子を取得するプロパティ.
Paper.getById
図形要素の内部識別子を使って要素を取得する.
Element.insertAfter
図形要素を指定した要素の前に挿入する.つまりその要素の下に描画 される.insertAfter(element)
Element.insertBefore
図形要素を指定した要素の後に挿入する.つまりその要素の上に描画 される.insertBefore(element)
Element.prev
要素ツリーにおける前の要素への参照.つまりその要素の下の要素 を取得する.一番下の要素はnullを返す.
Element.next
要素ツリーにおける次の要素への参照.つまりその要素の上の要素 を取得する.一番上の要素はnullを返す.
Element.remove
図形要素を描画対象から外す(Paperオブジェクトの管理下から外す).
Element.data
図形要素に付加的な値を設定する.data(key)…値の取得,data(key,value)…値の設定
Element.removeData
図形要素に付加された値を削除する.removeData(key)
Element.paper
図形要素が所属しているPaperオブジェクト.
Element.node
図形要素に対するdom要素を取得する.node()
このうちElement.nodeメソッドはRaphaëlからよりネイティブなapiにアクセスするために重要な機能で,Raphael.typeでベクタグラフィックの判定処理と組み合わせれば特定の環境での機能を強化する(例えばSVGAnimateElementを追加する)と言った事が可能だ.なお,jQueryに存在した($(HTMLElement)のような)ネイティブapiからRaphaëlオブジェクトにアクセスすると言った機能は存在しない.
Created with Raphaël 2.1.0 [object SVGCircleElement] new data remove前:2154 remove後:2155
Raphael(function(){ //ノードのクローンとinsertBefore var paper = Raphael("canvasTree", 200, 200); var circle = paper.circle(50,50,40).attr("fill", "red"); var circle2 = circle.clone().attr({fill: "blue", cx: 70, cy: 70}); circle2.insertBefore(circle); //idによる検索と,dataメソッドによるカスタムデータの格納・参照 var temp = paper.circle(50,150,40); temp.data("key", "new data"); var id = temp.id; var circle3 = paper.getById(id); circle3.attr("fill", "green"); paper.text(50,150,circle3.node); paper.text(50,160,circle3.data("key")); if(Raphael.type == "SVG"){ circle3.node.style.setProperty("fill", "pink"); } //prev要素とnext要素 var circle4 = paper.circle(150,50,30).attr("fill", "yellow"); var circle5 = paper.circle(150,70,30).attr("fill", "orange"); var circle6 = paper.circle(150,90,30).attr("fill", "silver"); circle5.prev.attr({stroke:"red", "stroke-width":10}); circle5.next.attr({stroke:"gold", "stroke-width":10}); //removeメソッドとnext要素の相関 var circle7 = paper.circle(150,150,40).attr("fill", "purple"); //circle6の次の要素がcircle7の削除によりtext要素に切り替わっている! paper.text(150,160,"remove前:" + circle6.next.id); circle7.remove(); paper.text(150,170,"remove後:" + circle6.next.id); });
ユーティリティーメソッド群
Raphaelでは様々な変換メソッドが提供されている.path文字列やtransform文字列を構成するのに便利だ.
Raphael.is
typeof関数のエイリアス.
Raphael.angle
3頂点を指定し,2辺に挟まれた角の角度を取得する.angle(x1,y1,x2,y2,x3,y3)線分点1-点3と点2-点3が挟む角の角度.
Raphael.deg
ラジアンを角度に変換する.deg(rad)
Raphael.rad
角度をラジアンに変換する.rad(deg)
Raphael.format
文字列を構築する.fotmat([テンプレート],値0,値1…)
Raphael.fullfill
文字列を構築する.fullfill([テンプレート],連想配列)
Raphael.snapTo
値をグリッドに合わせる.grid値±toleranceの範囲内にvalueが含まれていた場合,grid値が返される.マウスなどで図形要素を移動させる際,描画の基準をグリッドに固定したい場合に用いる.snapTo(grid_values,value,tolerance)
Raphael.createUUID
RFC4122形式のUUID文字列を生成する.
Created with Raphaël 2.1.0 45 59.99999999999999 3.141592653589793 abc:10:def ghi:99:jkl 12 10 15
Raphael(function(){ var paper = Raphael("canvas_utils", 200, 200); var set = paper.add([ {type:"text", x: 100, y: 20, text: Raphael.angle(0,0,100,100,200,0)}, {type:"text", x: 100, y: 40, text: Raphael.deg(Math.PI/3)}, {type:"text", x: 100, y: 60, text: Raphael.rad(180)}, {type:"text", x: 100, y: 80, text: Raphael.format("{0}:{1}:{2}", "abc", 10, "def")}, {type:"text", x: 100, y: 100, text: Raphael.fullfill("{a}:{b}:{c}", {a:"ghi", b:99, c:"jkl"})}, {type:"text", x: 100, y: 120, text: Raphael.snapTo([5,10,15],12,1)}, {type:"text", x: 100, y: 140, text: Raphael.snapTo([5,10,15],12,2)}, {type:"text", x: 100, y: 160, text: Raphael.snapTo([5,10,15],12,3)} ]).attr({fill:"blue", "font-size": 20}); });
環境に関わるメソッド
クロスブラウザを謳うRaphaëlではあるが,環境によっては描画に不具合が出るケースがある.そのような不具合を解消するためのメソッドが提供されている.なお全ての問題が解決するわけではないので,あくまでサポート的な用途として捉える.
Raphael.svg
ブラウザがsvgをサポートするかを判定する.
Raphael.vml
ブラウザがvmlをサポートするかを判定する.
Raphael.type
ブラウザがサポートするベクタグラフィックの種類を取得する.type()SVG/VML
Raphael.renderfix
Firefox,ie9におけるサブピクセルのレンダリングに関わる不具合を解消する.
Raphael.safari
safari(webkit)におけるレンダリングの不具合を解消する.webkitではアニメーション処理後に稀に描画のゴミが発生する.このゴミを消すために画面を再描画する.
Created with Raphaël 2.1.0 true false SVG
Raphael(function(){ var paper = Raphael("canvas_f", 200, 200); var set = paper.add([ {type:"text", x: 100, y: 20, text: Raphael.svg}, {type:"text", x: 100, y: 40, text: Raphael.vml}, {type:"text", x: 100, y: 60, text: Raphael.type} ]).attr({fill:"blue", "font-size": 20}); });
機能の拡張
jQuery等のライブラリと同様にRaphaëlにおいても機能の拡張を行うための仕組みが提供されている.よく使う操作を登録しておくことで,開発効率が上がることだろう.
Raphael.fn
Paperオブジェクトに独自メソッドを定義する.
Raphael.st
Setオブジェクトに独自メソッドを定義する.
Raphael.el
図形オブジェクトに独自メソッドを定義する.
Paper.customAttribute
カスタム属性を定義する.この属性はElement.attr,Elemnet.animateメソッド等から呼び出すことが出来る.
Paper.ca
customAttributeへのショートカット.
Raphael.ninja
callするとグローバル変数Raphael変数を削除する.万が一の名称の衝突を解消する為の機構.
実行例:(function(raphael){var paper = raphael("canvas", 200,200);//Raphaelを使った処理})(Raphael.ninja());
Created with Raphaël 2.1.0 ●本日は晴天なり
//Paperオブジェクトのもつメソッドを拡張 //これらの内容をRaphaelプラグインとして事前に実行しておくことで,カスタムメソッドを利用することができる. //NOTE:Raphael.fnはPaperオブジェクトのプロトタイプを参照している. Raphael.fn.line = function (x1, y1, x2, y2) { return this.path(Raphael.format("M{0},{1} {2},{3}", x1,y1,x2,y2)); }; //図形オブジェクトのもつメソッドを拡張 Raphael.el.red = function(){ this.attr({fill: "#f00"}); }; //Setオブジェクトのもつメソッドを拡張 Raphael.st.red = function () { this.forEach(function(el){ el.red(); }); }; Raphael(function(){ var paper = Raphael("canvas_ex", 200, 200); paper.line(50,50,150,50).attr("line-width", 5); paper.set(paper.circle(50,100,30),paper.rect(100,100,80,40)).red(); //カスタム属性で複雑な記述を簡略化 paper.customAttributes.before = function(text){ return {text: text + this.attr("text")}; }; paper.text(50,160,"本日は晴天なり").attr({"text-anchor":"start",before: "●"}); paper.customAttributes.strokeStyle = function (color,width,dash) { return {stroke: color, "stroke-width": width, "stroke-dasharray": dash}; }; paper.line(50,180,150,180).attr({strokeStyle:["green", 5, "-."]}) });
機能の拡張はプラグインの形をとって様々なものがweb上で配布されている.目的にあったプラグインを導入することで,作業効率を大幅に上げることが出来るだろう.
svg画像をRaphaëlで読み込む方法
Raphaël自体にsvg画像をロードする機能は存在しないが,事前にsvg画像ファイルをRaphaëlで処理可能なjson形式に変換することでブラウザ上に表示させることは可能である.svgは単なるxmlファイルであるから,テキストファイルを扱えるプログラム言語であれば,原理的にはどのようにでも変換可能だが,ここではxslを使った変換を紹介する.xslはxml文書に対する見た目を制御する言語だが,プレーンテキストの出力も可能であり,この機能を用いてsvgからjsファイルへ変換するのが最も簡単だと思われる.なお,xslの使い方についてはスタイル指定の項で触れたので,そちらを参照して欲しい.
下記はsvgファイルからpath要素を取得し,fill,stroke,stroke-width,opacity,fill-opacity,stroke-opacigyの各属性をRaphaëlで読み込み可能なjson形式にするxslのサンプルコードである.機能を必要最低限度に絞った分,コード量はかなり少なめである.
<?xml version="1.0" standalone="no"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:svg="http://www.w3.org/2000/svg">
<xsl:output method="text" encoding="utf-8" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="svg:svg">
//svgからpath要素を抽出してRaphaelで読み込み可能なjson形式に変換する.
var elements = [{type:"path", path:"m 0,0"}
<xsl:apply-templates/>
];
</xsl:template>
<xsl:template match="svg:path">
,{type:"path",path:"<xsl:value-of select="string(@d)"/>",
<!--fill-->
<xsl:if test="not(@fill)">
fill:"black",
</xsl:if>
<xsl:if test="@fill">
fill:"<xsl:value-of select="string(@fill)"/>",
</xsl:if>
<!--stroke-->
<xsl:if test="not(@stroke)">
stroke:"none",
</xsl:if>
<xsl:if test="@stroke">
stroke:"<xsl:value-of select="string(@stroke)"/>",
</xsl:if>
<!--stroke-width-->
<xsl:if test="@stroke-width">
"stroke-width":"<xsl:value-of select="string(@stroke-width)"/>",
</xsl:if>
<xsl:if test="not(@stroke-width)">
"stroke-width":"1",
</xsl:if>
<!--opacity-->
<xsl:if test="@opacity">
opacity:"<xsl:value-of select="string(@opacity)"/>",
</xsl:if>
<xsl:if test="@fill-opacity">
"fill-opacity":"<xsl:value-of select="string(@fill-opacity)"/>",
</xsl:if>
<xsl:if test="@stroke-opacity">
"stroke-opacity":"<xsl:value-of select="string(@stroke-opacity)"/>"
</xsl:if>
id:"<xsl:value-of select="string(@id)"/>"
}
</xsl:template>
</xsl:stylesheet>
これを任意のsvgに適用して目的のjsonコードを得るのだが,その際に注意すべき点がある.これらの処理は事前にドローイングツールのsvg出力設定で指定可能な場合がある.
path要素だけで画像を作る.処理対象の要素を絞ることでxsl等の処理を簡略化することが出来る.なおrect要素やcircle要素も同様にして処理可能だがここでは省略した.
style属性を使わない.style属性で見た目を設定していた場合,内容を分解せねばならず,xsl等でRaphaël形式のjsonコードに変換する際の妨げとなる.(この意味でinkscapeとRaphaëlとは食い合わせが悪い.inkscapeではstyle属性での色の指定しかできない.)
フラットな構造とする.グループ化は最終的に解除する.
svgとRaphaëlとで仕様が異なるため,fillやstrokeにはグラデーションなどの設定は使わないで,単色での指定をする.
何れも短いコードで実用的な処理結果を得るためのものだが,もちろんもっと複雑な処理を行うことも出来る.しかしそのような処理はsvgの仕様をjavascriptで再定義している事にほかならない.事前にツールによる出力結果に制約を掛ける方が得策である.
なおjsonデータを一旦elementsグローバル変数に格納し,paper.addメソッドに渡しているが,コールバック関数を使った処理としても良い.要件によって自由に書き換えて使って欲しい.実際はかなりの制約が存在するので利用できる場面は限られてしまうが,憶えておいて損はないだろう.
Created with Raphaël 2.1.0
//jQueryを使っています. $("<script>") .attr("type", "text/javascript") .attr("src", "tiger.js") .appendTo("head"); Raphael(function(){ var paper = Raphael("canvas_from", 200, 200).setViewBox(0,0,500,500,false); paper.add(elements); });
svgdomとの連携
レガシーie環境との互換性を無視することとなるが,Raphaëlをsvg図形描画のユーティリティーとして利用し,svgdomによる描画処理とを混在させることもできる.Raphaël内部で管理されているグラフィック構造を破壊しないようにする必要があるが,Raphaël単体では無理な機能(例えば外部svgファイルで設定されているグラデーションやクリップパス情報の参照等)をsvgdomの機能を使って実現することが可能である.
Created with Raphaël 2.1.0
Raphael(function(){ var paper = Raphael("raphael_dom", 200, 200); var rect = paper.rect(20, 20, 160, 160); var domElem = rect.node; domElem.style.setProperty("fill", "red"); domElem.style.setProperty("stroke-width", "0"); domElem.style.setProperty("clip-path", "url(#cp)"); });
この他にもRaphaëlが生成したsvgソースを取得すると言ったこともできる.