Blogで0.0.1のマイナーアップデートがリリースされた(図001)。大きな変更が加えられたのはEaselJSのイベントモデルだ。0.7.1が公開される」参照)。0.7.0のイベントモデルの改訂では、EventDispatcherクラスがイベントリスナーを扱うほとんどのクラスから継承されることとなり、またイベントオブジェクトの基本クラスとしてEventクラスが新たに定められた(「EaselJS次期バージョンのイベントモデル改訂」)。実際のスクリプティングで大きく変わったのは、マウスイベントの扱いだ。0.6.1と異なり、0.7.0からマウスイベントは基本的にDisplayObjectクラスで扱うようになった。0.7.0でインスタンスをドラッグ&ドロップする」)。0.6.1では、イベントリスナーを加えるクラスがDisplayObjectとMouseEventのふたつに分かれていた。0.6.1のドラッグ&ドロップで扱うマウスイベントインスタンスの上でマウスボタンが押されたらドラッグを始めるDisplayObject.mousedownイベントに加えたリスナーが呼出されるリスナー関数はイベントオブジェクトにふたつのイベントのリスナーを加えるMouseEvent.mousemoveイベントMouseEvent.mouseupイベントボタンを押したままマウスを動かしたらインスタンスが追いかけるMouseEvent.mousemoveイベントに加えたリスナーが呼出されるマウスポインタの座標にインスタンスの位置を合わせるマウスボタンを放したらドラッグは終えるMouseEvent.mouseupイベントに加えたリスナーが呼出されるリスナー関数はイベントオブジェクトからふたつのイベントのリスナーを除くMouseEvent.mousemoveイベントMouseEvent.mouseupイベントつぎのコード01-001では、リスナーを後で除くために、イベントオブジェクトの参照を変数(dispatcher)にもたせている(第1および第4行目)。0.7.0からは、マウスイベントのリスナーを加えるインスタンスはDisplayObjectにまとめられた。リスナーを除くために、イベントオブジェクトの参照をもたなくて済む。イベント名が変わったことに注意しなければならない(表001)。0.7.0には、MouseEventクラスのイベントがなくなった()。インスタンス上でマウスボタンが押された。インスタンス上でマウスボタンが押されたまま、ポインタが動かされた。インスタンス上で押されたマウスボタンが放された。つぎのコード01-002では、イベントリスナーを加えたインスタンスはEvent.targetプロパティから得られるので、その参照からリスナーを除いている(第14〜16行目)。イベントのバブリングバブリングというのは、泡のように上っていくこと。0.7.0から、ほとんどのマウスイベントは、イベントが起こったインスタンス(ターゲット)から表示リストの階層を上って伝わるようになった。その流れに先立って、表示リストの頂点からターゲットに向かって伝わることをキャプチャという(図002)。図002■イベントのバブリングとキャプチャイベントの流れは止められる。バブリングが採り入れられたのに合わせて、Eventクラスにはイベントの流れを扱うメソッドが備わった(表002)。イベントが起こったときに行われるべきデフォルトの動きを止める。イベントが後続のリスナーに伝わるのを止める。イベントが後続のオブジェクトに伝わるのを止める。Event.stopImmediatePropagation()メソッドはイベントを直ちに(immediate)止めて、リスナーが加えられた現行オブジェクト(Event.currentTargetプロパティの参照)のつぎのリスナーにもイベントは送らない。Eventクラスでイベントの流れを扱うメソッド」参照)。図003■現行オブジェクトと表示リストのつぎの階層へのイベントの流れサンプルとして後にコード01-003を掲げた。ボタンにするShape(button)と背景画像のBitmapインスタンスを、Containerオブジェクト(container)に入れ子にした。ボタンをクリックするとURLが開き、親Containerオブジェクトは画像とボタンをまとめてドラッグできるようにしたい(図004)。図004■ボタンのクリックと背景画像のドラッグボタンはクリック背景画像はドラッグドラッグのスクリプトは、基本的に前掲コード01-002と変わらない。ドラッグするインスタンスは、マウス操作をしたBitmapではない。このインスタンスをボタン(button)とともに入れ子にした親Containerオブジェクト(container)だ。イベントが起こったインスタンスはEvent.targetプロパティで参照を得るのに対して、リスナーが加えられてイベントを処理しているインスタンスはEvent.currentTargetプロパティで参照する(第6、第13、および第19行目)。ボタンのShapeインスタンス(button)については、DisplayObject.clickイベントに加えたリスナー関数(navigateToURL())でURLを開けばよい(第3および第23〜25行目)。このスクリプトの組立てだけでは、意図しない動きが生じる。Shapeインスタンスの上でマウスボタンを押すと、イベントはバブリングして親Containerオブジェクト(container)に伝わる。すると、そのリスナー関数(startDrag())が呼出され、ドラッグを始めたことになる。ひとたびボタンを押すと、Shapeインスタンスの外にマウスポインタが出せなくなってしまうのだ。"_blank");}そこで、マウスイベントのバブリングを止める。ボタンのShapeインスタンス(button)にも、DisplayObject.mousedownイベントのリスナー(stopEvent())を加える(第4行目)。この関数がやることは、Event.stopPropagation()メソッドで親オブジェクトへのイベントを止めることだけだ(第26〜28行目)。0.7.1で入れ子インスタンスのマウスイベントのバブリングを止めるマウスイベントのバブリングについて、ひとつ気をつけるべきことがある。0.7.1でつぎのようなコードを書いたとき、手前に重ねたインスタンスがリスナーをもたなければ、後ろのインスタンス(instance)にマウスイベントが起こってそのリスナー(rotate())は呼出される。インスタンスの重なりをクリックすれば、後ろのインスタンスが回る(図005)。30;stage.update();}図005■手前に重ねたインスタンスがリスナーをもたなければ後ろのインスタンスにイベントが起こるところが、以下のコード01-004のように、ふたつのインスタンスがマウスイベントのリスナーをもつ親Containerオブジェクト(container)の入れ子になっていたとする(第2〜4行目)。すると、手前のインスタンス(no_listener)は自らにリスナーがなくても、マウスイベントを起こしてバブリングしてしまう。インスタンスが重なった部分をクリックしたマウスイベントは、前のインスタンスが奪って、後ろのインスタンス(instance)のリスナー(rotate())に渡らなくなる(図006)。0.7.0のEventDispatcherクラスには、新たにメソッドがふたつ加わった(表003)。これらのメソッドと関わるEventクラスの新しいメソッドも併せてご紹介する。イベントリスナーを扱うEventDispatcherクラス」をお読みいただきたい。第1引数のイベントに第2引数のリスナーを加える。残りの引数で、さらに細かな定めが加えられる。EventDispatcher.addEventListener()メソッドの拡張。第1引数のイベントに加えた第2引数のリスナーを除く。EventDispatcher.removeEventListener()メソッドと同じ。そのイベントオブジェクトが渡されたリスナー関数をイベントリスナーから除く。例として、EaselJSとPreloadJSで外部画像ファイルを読込む場合について考える。0.3.1でロードした画像のBitmapインスタンス(instance)をステージに配置する処理の枠組みだ。0.4.1でも、とくに問題なく動作する。draw);stage.update();}ここで問われるのは、LoadQueue.fileloadイベントのリスナー(draw())からどうやって画像が読込まれたBitmapインスタンスを参照するかだ。このコード01-005では、Bitmapインスタンスの参照をLoadQueue.loadFile()メソッドの引数のオブジェクトにdataプロパティとして与えた(第3行目)。すると、LoadQueue.fileloadイベントのリスナーが引数に受取るイベントオブジェクトのitemプロパティから取出せる(第7行目)。EventDispatcher.on()メソッドには、第3引数でリスナー関数がthis参照とすべきオブジェクトを定められる。すると、リスナー関数から参照したいインスタンスは、このメソッドの第3引数に与えることもできる。this参照先)前掲コード01-005で、EventDispatcher.on()メソッドを用いれば、第3引数によりBitmapインスタンスをリスナー関数(draw())のthis参照に定められる。後に掲げるコード01-006には、その書替えを施した(第2〜3行目)。data:instance});loader.loadFile({src:file});これで、LoadQueue.fileloadイベントのリスナー関数(draw())は、Bitmapインスタンスをthis参照で扱える。リスナーを除くときは気をつけなければならない。EventDispatcher.on()メソッドがリスナーに加えるのは、第2引数の関数から第3引数のオブジェクトをthis参照にして新たにつくる関数だ。リスナーを除くメソッドに、第2引数の関数を渡してもイベントからは消せない。ふたつの関数が別だからだ。関数がリスナーではないため除けないstage.update();}EventDispatcher.on()も、EventDispatcher.addEventListener()メソッドと同じく、インスタンスに加えたリスナーを返す。その参照をとっておいて、イベントリスナーから除けばよい。今回のお題は、Bitmapインスタンスの参照を使わないのが売りだった。替わりにリスナー関数の参照をもたなければならないのでは意味がない。そこで使えるのが、新たなEvent.remove()メソッドだ。そのイベントオブジェクトを受取った関数が、リスナーから除かれる。イベントオブジェクトに対してメソッドを呼出すので、リスナー関数の参照は求められない(第6行目)。これで、Bitmapインスタンスもリスナーも、参照をとっておかなくて済む。draw);eventObject.remove();stage.update();}ひとつ気をつけなければならないのは、Event.remove()メソッドが直ちにイベントリスナーを消すのではないことだ。そのイベントリスナーをもう呼出さないというフラグが与えられる。内部的には、つぎにイベントが配信されるとき、フラグのついたリスナーが除かれる。そのため、Event.remove()メソッドを呼出したすぐ後にEventDispatcher.hasEventListener()メソッドでリスナーがあるか調べると、trueが返されてリスナーは残っている。そもそも、EventDispatcher.on()メソッドに第3引数を渡すと、呼出すたびに新たなリスナー関数がつくられる。したがって、第3引数を与える呼出しは、できるだけ少なくすることが望ましい。0.7.1では、イベントに関わるつぎのようなメソッドとプロパティが加えられた(表004)。指定したイベントをフローの中で受取るリスナーがあるかどうかをブール(論理)値で返す。イベントリスナーが加えられたオブジェクトから見たマウスポインタのxy座標値。0.7.0に備わったMatrix2D.transformPoint()メソッドは、Matrix2Dオブジェクトの変換行列でxy座標を変換して返す。gihyo.jp連載「HTML5のCanvasでつくるダイナミックな表現—CreateJSを使う」の第15回「Matrix2Dクラスで座標を回す」と第16回「3次元空間で座標を回す」でつくった作例にもとづいて、メソッドの使い方をご紹介したい。Pointオブジェクトのxy座標を変換するMatrix2D.transformPoint()メソッドMatrix2D.transformPoint()メソッドは参照したMatrix2Dの変換行列で、はじめのふたつの引数に渡したxy座標を変換する。第3引数にPointなどのオブジェクトを渡せば、そのxとyのプロパティに対して変換された座標値が与えられる。第3引数を省くと、新たなオブジェクトにxyのプロパティ値が納められて返される。Pointオブジェクト)以下のサンプル003はつぎの関数(rotate())で、配列(points)に入れた星形の頂点座標のPointインスタンスをすべてforループで取出し、Matrix2D.transformPoint()メソッドにより回転している。変換行列による回転は、数多くの座標を同じ角度回すとき効率に優れる(行列演算では「三角関数で座標を回転するふたつの計算方法」の2の式を用いている)。Matrix2D.identity()メソッドは、Matrix2Dオブジェクトを初期化する。オブジェクトは使い回した方がお得だ。point);}}サンプル003は、マウスポインタの水平位置によって星形の頂点座標を回す角度の速さは変えて、頂点を直線で結んでいる(コードの詳しい解説は「Matrix2Dクラスで座標を回す」参照)。Matrix2D.transformPoint()メソッドを3次元空間の回転に応用するつぎに、同じ星形をy軸で回す。CreateJSに3次元座標を扱うクラスはない。そこで、座標はObjectインスタンスとし、xyzのプロパティをもたせる。z座標値は初め0でよい。z:0}座標をy軸で回すということは、y座標の値は変えず、残るふたつの座標値をxz平面で変換するということだ(図007)。図007■xz平面で座標を回す以下のサンプル004はつぎの関数(rotate())で、配列(points)に入れた星形の3次元頂点座標のObjectインスタンスをすべてforループで取出す。そのxz座標をMatrix2D.transformPoint()メソッドにより回転している。そのうえで、変換したPointインスタンス(_point)のxy座標値を、3次元座標のObject(point)のxとzプロパティに与えた。座標を変換するPointオブジェクトは、ただObjectの3次元座標に値を戻す中継用なので、予め変数にひとつつくって使い回している。_point.y;}}サンプル004は、このようにして3次元頂点座標をy軸で回している。星形のワイヤーフレームを描くには、透視投影の座標計算をしないと遠近感が加わらない。それらのコードの詳しい解説については、「3次元空間で座標を回す」をお読みいただきたい。Ticker.tickイベントのタイミングにrequestAnimationFrameを用いるTckerクラスはタイミングのAPIに、setTimeoutかrequestAnimationFrameを使うことができる。0.7.0では、タイミングを決めるプロパティとしてTicker.useRAFに替わってTicker.timingModeが備わり、3つのモードから選べるようになった。Ticker.timingModeプロパティTicker.timingModeは、Ticker.tickイベントのタイミングをsetTimeoutとrequestAnimationFrameのどちらのAPIで決めるか、および用いるモードを示す静的プロパティだ。つぎの3つの定数から選ぶ(表001)。setTimeoutは間隔のばらつきが小さく、requestAnimationFrameはブラウザの描画とタイミングが合う。ブラウザがrequestAnimationFrameのAPIをサポートしていないときは、Ticker.TIMEOUTモードが用いられる。フレームレートに合わせた時間間隔(デフォルト)。フレームレートによらず、requestAnimationFrameの呼出しにもとづく。アニメーションは経過時間を調べて進める。フレームレートに合わせてTicker.tickイベントを間引く。基本となるリフレッシュレート60Hzの約数にフレームレートを定めないと効果は少ない。アニメーションをrequestAnimationFrameで行うTicker.timingModeプロパティをTicker.RAFモードにすると、Ticker.tickイベントの間隔がばらつく。その調整をしたい場合、Eventオブジェクトのdeltaプロパティから経過ミリ秒数が得られる。フレームレート(FPS)を基準にして変化を与えるなら、つぎの調整係数を乗じればよい。1000つぎのコード03-001は、フレームレートを変数(fps)に与え、フレームあたりの角度(5)にEventオブジェクトのdeltaプロパティからから求めた上記調整係数を乗じて、インスタンス(instance)が回転する速さを保っている。0.7.1は0.0.1のアップデートなので、バグ修正が多くを占める。0.7.0から直された大きなバグを2件ご紹介する。Ticker.on()メソッドが関数を返さないTicker.addEventListener()とTicker.on()は、ともにイベントリスナーを加える静的メソッドだ。どちらも、登録されたリスナー関数を返すと定められている。0.7.0では戻り値はundefinedで、関数が返されない。0.7.0のTicker.addEventListener()メソッドはつぎのように実装されている。関数を返すreturnステートメントがない。Ticker.on()メソッドも内部的にTicker.addEventListener()を呼んでいるので、同じ問題が生じる。0.7.0のTicker.addEventListener()やTicker.on()メソッドが関数を返さない」参照)。ColorMatrixクラスのバグ3種盛りColorMatrixクラスはカラー変換行列を表し、ColorMatrixFilterのカラー変換に用いられる。バグが確認されているColorMatrixクラスのメソッド」)。ColorMatrixクラスでバグが修正されたメソッドColorMatrix.toArray()ColorMatrix.concat()ColorMatrix.clone()ColorMatrix.toArray()メソッドColorMatrix.toArray()とColorMatrix.concat()のバグは同じ原因なので、確かめやすい前者のメソッドで見てみよう。カラー変換行列は5行×5列の正方行列で、ColorMatrix.toArray()メソッドはその25成分をエレメントに納めた配列にして返す。ところが、戻り値は空の配列になってしまう。0.7.0のColorMatrix.toArray()メソッドは、実質的につぎのように実装されていた。ColorMatrix()コンストラクタ関数のFunction.prototypeプロパティにArrayオブジェクト(配列)を与えている(第4行目)。これでそのオブジェクトのクラスは継承できるのが原則だ。ところが、Array.lengthプロパティが正しい値に書替えられないため、ColorMatrix.toArray()メソッドから内部的に呼出されるArray.slice()メソッドが25成分値を取出せない。0.7.1のColorMatrixクラスは、もはやArrayクラスを継承しない。ColorMatrix.toArray()メソッドは、ColorMatrixオブジェクトがもつカラー変換行列の成分値をforループで取出して、新たな配列に納めたうえで返すように修正された(第2〜6行目)。no.119「スクリプト覚え書き」参照)。arr;};ColorMatrix.concat()メソッドColorMatrix.concat()メソッドは、引数のカラー変換行列と合成(乗算)する。引数のカラー変換行列は、配列またはColorMatrixオブジェクトで渡す。0.7.0のColorMatrix.concat()メソッドは、引数にColorMatrixオブジェクトを渡すと行列演算が行われない。0.7.0のColorMatrix.toArray()メソッドは、実質的につぎのように実装されていた。内部的には、引数を25エレメントの配列として扱うために、データの型を整えるメソッド(ColorMatrix._fixMatrix())が呼ばれる。このメソッドが、ColorMatrixオブジェクトから配列に換えるときArray.slice()メソッドを用いるため、ColorMatrix.toArray()メソッドと同じ理由で正しい演算が行われない。0.7.1では、ColorMatrixオブジェクトを配列に換えるとき、ColorMatrix.toArray()メソッドを呼出すよう修正された。matrix;};ColorMatrix.clone()メソッドColorMatrix.clone()メソッドは、同じカラー変換行列のColorMatrixオブジェクトを新たにつくって返す。0.7.0のColorMatrix.clone()メソッドは、実質的につぎのように実装されていた。内部的に呼出しているColorMatrix()コンストラクタに渡す引数は、ColorMatrixオブジェクトひとつでなく、つぎのような4つの数値でなければならない。引数の数とそのデータの型が違っているのだから、正しいオブジェクトは返らない。0.7.1のColorMatrix.clone()メソッドは、コンストラクタでつくった新たなColorMatrixオブジェクトに、ColorMatrix.copyMatrix()メソッドでカラー変換行列の成分値をコピーするように修正された。