前回は、BoxBlurFilterによりぼかしたイメージのインスタンスに、AlphaMaskFilterでマスクしたもとのイメージのインスタンスを重ね、マウスクリックした部分のアルファを上げていくという表現までつくった(第8回コード3)。参考までに、jsdo.itのサンプルを掲げておく。
今回は、ドラッグでアルファマスクを描くようにしよう。目指すは、EaselJSサイトのデモ「ALPHAMASK FILTER」だ。
マウスポインタの座標を直線で結ぶ
ドラッグしている間マウスポインタの座標を調べ、アルファマスクのShapeインスタンスにその軌跡を描く。座標は直線で結ぶことにしよう。直線を引くのに使うメソッドは、Graphics.moveTo()とGraphics.lineTo()だ。まず、描き始めの座標をGraphics.moveTo()メソッドによって決める。その後、Graphics.lineTo()メソッドで座標を直線で結んでいく。また、線のスタイルや色は、それぞれGraphics.setStrokeStyle()とGraphics.beginStroke()メソッドで定める(「EaselJSのGraphicクラスで2次ベジエ曲線を描く」参照)。
スクリプトは前回のコード3に手を入れていく。マウスイベントの扱いは、画像を読込み終えたLoadQueue.fileloadイベントのリスナー関数(draw())に加える。ステージ上でマウスボタンを押すStage.stagemousedownイベントでドラッグが始まり(startWipe())、ボタンを放すStage.stagemouseupイベントによりドラッグは終わる。ドラッグしている間のアルファマスクのアニメーションは、Ticker.tickイベントのリスナー(wipe())が行う。
リスナー関数は前述3つのイベントごとに役割を分けて、以下のように書替える。Stage.stagemousedownイベントのリスナー(startWipe())は、描き始めのマウスポインタ座標をPointオブジェクトの変数(mousePoint)に納める。また、ドラッグが始まったことを示す変数(isDrawing)はtrueにした。そして、アルファマスクを描くGraphicsオブジェクトには、Graphics.setStrokeStyle()メソッドで線のスタイルだけ定めた。マスクそのものは、ドラッグの関数(wipe())が描く。なお、メソッドの第2および第3引数で、線の端と角を丸く("round")した(引数について詳しくは前出「EaselJSのGraphicクラスで2次ベジエ曲線を描く」参照)。
ドラッグでマスクを描く関数(wipe())は、Ticker.tickイベントのリスナーに定めた。ドラッグしていることは、変数(isDrawing)をif条件で確かめてから、描画に入る。マウスポインタの座標は予めローカル変数(mouseXとmouseY)にとっておく。そして、アルファマスクのGraphicsオブジェクトにアルファつきのカラーを定めて、マウスポインタの前の座標から今の座標までの直線を描いている。そして、つぎの線描のために、マウスポインタ座標はPointオブジェクトの変数(oldPoint)に納めた。
マウスボタンを放すStage.stagemouseupイベントでドラッグは終わる。そこで、ドラッグしていることを示す変数にfalseを与える。これら3つのイベントリスナーを定めることにより、アルファマスクがドラッグで描ける(図1)。
script要素に書いたJavaScript全体は、つぎのコード1のとおりだ。ただし、前掲のコードではマスクに軌跡が正しく描かれているかを確かめるため、線は細くしてアルファも高めにした(図1)。コード1は、目指す表現に合わせて線は太く、アルファも下げてある(図2)。
このコードでいいのか?
前掲のコード1を見て、このままでいいのか、もう少し手を加えたほうがすっきりするのでは、と考えた読者もいるだろう。最近のTVドラマの主人公がよく口にした台詞で答えるなら、「いいね~」と申し上げたい。ふたつ押さえておこう。
ひとつ目は、Ticker.tickイベントのリスナー(wipe())だ。マウスをドラッグしているかどうかは変数(wipe)にブール値を入れ(いわゆる「フラグ」)、ドラッグしていないときは何のアニメーションもしない。しかし、Ticker.tickイベントそのものは、何もしなくても起こり続ける。それなら、そもそもイベントリスナーを除いてしまって、マウスボタンが押されたときに改めてリスナーを加えれば無駄がない。
まったくそのとおりだ。けれど、実はこの後マウスポインタとともに動くカーソル替わりのインスタンスを加える。このインスタンスは、マウスボタンに関わりなくマウスポインタの後を追い続ける。そのため、Ticker.tickイベントのリスナー関数(wipe())は、そのまま残した。
もうひとつは、Graphicsオブジェクトに線を描く処理の切り分けだ。Stage.stagemousedownイベントのリスナー(startWipe())は、描く線のスタイルをGraphics.setStrokeStyle()メソッドで決めた。だが、Graphics.beginStroke()メソッドによるカラーの指定や、Graphics.moveTo()メソッドの座標決めも、Stage.stagemousedownイベントのリスナーで初めにやってしまえば済むのではないだろうか。すると、つぎのようにTicker.tickイベントのリスナー(wipe())は、Graphics.lineTo()メソッドでひたすら直線を引き続ければよい。
鋭い指摘だ。筆者も初めはそう考えた。しかし、このコードでは、アルファマスクにマウスポインタの軌跡は正しく描けるものの、表現が意図どおりにならなかった。具体的には、アルファのかかった線を塗り重ねてもアルファが高まらず、もとイメージが濃くならないのだ。重なりのアルファを上げるためには、Graphics.beginStroke()メソッドを呼出して仕切り直さないといけない。すると、描き始めの座標も、改めてGraphics.moveTo()メソッドで決めることになる。
ということで、前掲コード1はこのまま生かすことにする。ただ、書いたコードを見直して、推敲を重ねることはよい習慣といえる。
カーソル替わりのインスタンスをマウスポインタに合わせて動かす
つぎは、カーソル替わりのShapeインスタンスをつくり、マウスポインタに合わせて動かしたい。アルファマスクに描かれる線の太さと同じ円形にすれば、描かれるマスクが予想しやすくなる。また、マウスポインタそのものも、矢印からかたちを変えたい。
カーソル替わりのShapeインスタンス(cursor)は新たに定める関数(createCursor())でつくり、描画の関数(draw())から呼び出す。インスタンスのアルファは半透明にした。そして、インスタンスにポインタを重ねたときのカーソルのかたちは、DisplayObject.cursorプロパティで定める。"pointer"は、指差しカーソルだ。
インスタンス(cursor)のアニメーションは、Ticker.tickイベントのリスナー関数(wipe())で扱う。マウスポインタの座標を調べて、インスタンスの位置をそこに合わせれればよい。ただし、マウスをドラッグしないと、キャッシュを書替える関数(updateCacheImage())が呼ばれない。この関数はStage.update()メソッドの呼出しを含む。そこで、else文にStage.update()メソッドのステートメントを加えた。
これでカーソル替わりのインスタンス(cursor)は、つねにマウスポインタに合わせて動く。ところが、ポインタのカーソルが矢印のまま変わらない。実は、ポインタをインスタンスに重ねたとき、そのカーソルを変えるには、Stage.enableMouseOver()メソッドを呼び出さなければならない[1]。
これで、カーソル替わりのインスタンス(cursor)はマウスポインタに合わせて動き、ポインタのかたちは矢印から指差しカーソルに変わる(図3)。script要素に書いたJavaScriptの全体をコード2にまとめた。
これで、ひとまずお題の動きはできた。jsdo.itにもサンプルを掲げた。次回は、EaselJSサイトのデモ「ALPHAMASK FILTER」のコードと違いを見比べながら、さらに知識を深めよう。