JavaScript「EaselJSでインスタンスをクリックした座標で回しながらドラッグする」では、初めにマウスボタンが押された座標でインスタンスを回しながらドラッグしてみました。ドラッグのアニメーションはMouseEvent.onMouseMoveイベントハンドラで行ったため、マウスを動かしたときだけインスタンスが回りました。本稿では、マウスをドラッグする勢いで回転が速まり、止めれば減速して、ドロップするとゆっくり止まるアニメーションをつくることにします(サンプルHTMLドキュメント)。ドラッグをしている間同じ速さで回り続けるアニメーションビットマップはPNGファイル(card.png)で用意しました。HTMLドキュメントと同じ場所に画像用のフォルダ(images)をつくり、その中に納めます(図001)。HTMLドキュメントの<body>要素には<canvas>要素(id属性"myCanvas")が記述され、描画のためのJavaScriptの関数(xInitialize())を呼出しているものとします。height="180"></canvas></body>図001■PNGファイルを画像用フォルダに納めるまずは、前出「EaselJSでインスタンスをクリックした座標で回しながらドラッグする」のコード003をもとに、インスタンスが初めにクリックした座標を中心にして、ドラッグしている間同じ速さで回り続けるコードにしましょう(図002)。マウスボタンを押し続けていれば、ポインタは動かさなくてもインスタンスが回り、ボタンを放したら止まります。図002■クリックした座標を中心にインスタンスがドラッグしている間同じ速さで回り続ける前出「EaselJSでインスタンスをクリックした座標で回しながらドラッグする」のコード003と組立てが変わるのは、アニメーションはtick()メソッドによりフレームレートで進めることです。tick()メソッドではマウスポインタの座標がとれません。そこで、MouseEvent.onMouseMoveイベントのハンドラは、マウスポインタが動くたびにその座標を調べて変数に納めます。後に掲げる<script要素>全体のコード001からマウス操作にともなうアニメーションの処理を抜出したのがつぎのステートメントです。初めにマウスボタンを押したインスタンス上の座標が回転の中心になるように、DisplayObject.globalToLocal()とDisplayObject.localToGlobal()メソッドで補正する考え方については、前出「EaselJSでインスタンスをクリックした座標で回しながらドラッグする」をお読みください。offsetPoint.y;}インスタンス上でマウスボタンを押すDisplayObject.onPressイベントに設定したハンドラの関数(clickHandler())は、ドラッグに備えて支度を整えます(コード001第31〜40行目)。MouseEvent.stageXとMouseEvent.stageYプロパティで調べたマウスポインタの座標と、それをDisplayObject.globalToLocal()メソッドでインスタンスから見た値に変換して、それぞれPointオブジェクトで変数(lastMouseとorigin)にとります(第32〜35行目)。後者は回転の中心座標とします。マウス操作については、MouseEvent.onMouseMoveとMouseEvent.onMouseUpイベントにハンドラの関数(mouseMoveHandler()とmouseUpHandler())を定めます(コード001第36〜37行目)。アニメーションそのものはtick()メソッドで扱いますので、Ticker.addListener()メソッドにwindowオブジェクトをリスナーとして渡します(第39行目)。Tickerクラスを使ったアニメーションについては、「EaselJSで描いた星形を回す」をお読みください。MouseEvent.onMouseMoveイベントのハンドラ(mouseMoveHandler())は、動いたマウス座標をひたすら変数(lastMouse)に上書きするだけです(コード001第42行目)。MouseEvent.onMouseUpイベントのハンドラ(mouseUpHandler())は、ふたつのイベントハンドラを消して、Ticker.removeListener()メソッドの呼出しによりtick()メソッドのアニメーションを止めます(第45〜46行目)。tick()メソッドから呼出す関数rotate()が、初めにクリックした座標でインスタンスを回転します(コード001第52〜57行目)。今のところ、回す速さは一定(angularVelocity)です。回転の中心座標を定める方法は、前出「EaselJSでインスタンスをクリックした座標で回しながらドラッグする」をお読みください。<script>要素全体はつぎに掲げるとおりです。マウスの動きに応じて回す速さを増す振り回すようにドラッグしたとき、回転の勢いが増すようにしてみます。実際に滑らかなテーブルの上でカードのようなものを滑らせてみたとき、回りやすい動かし方があります。カードの中心(重心)から遠い位置に力を加えることです。真ん中をいくら激しく動かしても、なかなかカードは回りません。これは、てこの原理が働くからです。てこは支点から離れた位置(力点)をもった方が力はかかりやすくなります(図003)。つぎに、てこには垂直に力を加えるのがもっとも効率的です。自転車のペダルは水平になったとき踏み込む(力はペダルに垂直)と、加速しやすいのと同じです。図003■てこに力を加えるこのような回す力の加わりやすさは、物理学では「力のモーメント」という考え方で表されます。スクリプトに戻りますと、インスタンスの中心とドラッグしている座標を結ぶ部分がてこになります。ドラッグするマウスの動きがてこに加える力です(図004)。これらをベクトルとして捉えると、回す力はふたつの「ベクトルの外積」の大きさに比例します(詳しくはリンクの「力のモーメント」参照)。外積の計算式については後述します。図004■重心からのドラッグ位置とマウスの動きをベクトルとして捉えるベクトルはPointオブジェクトで扱えます。このベクトル(radius)は、直前のマウスポインタの座標(lastMouse)からインスタンスの真ん中に据えた基準点の座標を差引いて求めます[*1]。マウスポインタの座標の動きがもうひとつのベクトル(force)です。ベクトルの外積は、3次元空間で使われることが多く、ふたつのベクトルのどちらにも垂直な新たなベクトルとして求められます。2次元平面に垂直なのは必ずz軸で、そのxy座標はともに0です。したがって、2次元平面上のふたつのベクトルの外積は、z座標値だけで捉えられます[*2]。fy)だとすると、ふたつのベクトルの外積はつぎの式で導かれます。ryfxそれではマウスのドラッグする動きを、インスタンスが回る勢いとして加えてみましょう。前掲コード001につぎのような手を加えます。各行頭の番号は、後にまとめて掲げるコード002にもとづきます。point1.x;}MouseEvent.onMouseMoveイベントハンドラ(mouseMoveHandler())の仕事が大幅に増えました。マウスポインタの座標を変数に納めるのは変わっていません(コード002第34〜35行目および第40行目)。外積を求めるためのてこ(radius)とマウスの動き(force)のベクトルをPointインスタンスとしてつくり、外積計算の関数(crossProduct2D())に渡します(コード002第36〜38行目)。この関数が返す値の計算式(第57行目)は上述のとおりです。その値は角度として直に加えるには大きすぎるので、調整のための変数(RATIO)を設けて乗じました(第5および第39行目)。回転角(angularVelocity)の初期値は0にしています(第6行目)。インスタンスを回す関数(rotate())にも、以下のように1行加えました。回転角(angularVelocity)に減速率の変数(DECELERATION)を乗じて、回る速さを少しずつ下げています(コード002第54行目)。それにともない、MouseEvent.onMouseUpイベントのハンドラ(mouseUpHandler())から、アニメーションを直ちに止めるTicker.removeListener()メソッドの呼出しは除きました(第42〜44行目)。したがって、マウスボタンを放すとインスタンスの回転はすぐには止まらず、少しずつ速さを落としつつやがて止まります。DECELERATION;}前掲コード001に以上の修正を加えたのが、以下のコード002です。インスタンスをドラッグで振り回すと回転に勢いがつき、マウスボタンを放すと回る速さが遅くなって止まります。直前のマウスポインタの座標(lastMouse)は前掲コード001ではインスタンスを回す中心点になり、その位置がマウスのドラッグに合わせて動きます。3次元空間と2次元平面の外積については前出「力のモーメント」をお読みください。インスタンスをドロップしたとき慣性で移動させるもっとも、前掲コード002ではマウスボタンを放したとき、インスタンスは慣性でしばらく回り続けるものの、回転の中心座標が動かなくなります。そこで、インスタンスの移動についても、慣性を加えたいと思います。そのため、マウスボタンを放す直前のマウスの動き(force)を新たな変数(velocity)にとり、もともとマウス座標を納めていた変数(lastMouse)の値に減速しながら加えて行くことにします。手を加えるスクリプトはつぎのとおりで、各行頭の番号は後に掲げるコード003にもとづきます。DECELERATION;}MouseEvent.onMouseMoveイベントハンドラ(mouseMoveHandler())で、マウスの動き(force)を予め宣言した変数(velocity)に納めるようにしました(コード003第8行目および第44行目)。ドラッグ中はこの変数の値は使いません。マウスボタンを放した後、回転(rotate())に加えたい移動のアニメーションを新たな関数(updateInertia())で定めました(コード003第66〜72行目)。直前のマウスの動きを納めた変数(velocity)から取出したxy座標値を、マウス座標が納められた変数(lastMouse)に加えます(第67〜69行目)。これでtick()メソッドのアニメーションに、インスタンスの移動が加わります。そのうえで、マウスの動き(velocity)と回転(angularVelocity)の変数値を減じました(第70〜71行目)。移動のアニメーションを定めた新たな関数(updateInertia())は、回転の関数(rotate())からどのように呼出したらよいでしょう。インスタンスのドラッグ中には、この呼出しはしません。ひとつには、ドラッグ中かどうかを変数(フラグ)に置いて振分けることが考えられます。コード003は以下のように関数を変数(update)に納めることにしました。回転の関数(rotate())の最後にあった回転角(angularVelocity)を減じるステートメント(前掲コード002第54行目)は、別の関数(updateDrag())に分けました(コード003第63〜65行目)。変数(update)には、インスタンス上でマウスボタンを押したときこの関数を納めます(第33行目)。回転の関数からこの変数の関数を呼出します(第61行目)。そのうえで、マウスボタンを放したとき、移動するアニメーションの関数(updateInertia())で変数を上書きします(第47行目)。これで、ドラッグ中とドロップ後のアニメーションが切り替わることになります。DECELERATION;}コード003のスクリプト全体はつぎのとおりです。これでドラッグする勢いでインスタンスの回転が速まり、マウスボタンを放せば回転と移動が減速しつつやがて止まります(図005)。「インスタンスをドラッグする勢いで回転を加速する」というお題をどう扱ったらよいかという範囲では、コード003でよいでしょう。実際にコンテンツとして公開するとなると、ふたつ気になることがあります。第1に、外部画像を読込むのに、そのロード待ちをしていないことです。データがキャッシュされる前の、初めてコンテンツを開くとき、画像は表示されないかもしれません。これはPreloadJSで先読みをしておけばよいでしょう。解説は「PreloadJSで外部画像ファイルの読込みを待つ」をお読みください。第2に、インスタンスをひとたびドラッグしてアニメーションが始まると、その後Ticker.removeListener()は呼出されません。移動と回転の大きさが実質0になって、見た目アニメーションは行われていなくても、tick()メソッドは呼出され、アニメーションの処理と描画の更新をし続けることになります。そこで、慣性の移動ベクトルとしたPointオブジェクト(velocity)のxy座標と回転角(angularVelocity)それぞれの値の絶対値をMath.abs()メソッドで求め、合計が1未満になったときTicker.removeListener()メソッドを呼出しました(コード004第90〜91行目)。これで、アニメーションの動きがほとんどなくなると、スクリプトの処理(tick()メソッドの呼出し)もすべて終わります。