つぎなるお題は、マウスポインタの動きに沿って、滑らかに描かれては消える線のアニメーションだ。マウスが大きく動くと、線は太くなる。また、線はマウスポインタの軌跡そのままではなく、動きに少し弾みがついて描かれる。CreateJSコミュニティエバンジェリストのSebastian DeRossi氏がつくられたサンプル「Smooth Line」をもとに、表現は少し変えている。
マウスポインタの軌跡を滑らかな曲線で描く
はじめの一歩として、マウスポインタの軌跡を滑らかな曲線で描く。この連載をとおして読んでくださっている読者なら、どこかで聞覚えのある表現だと気づかれたかもしれない。第10回「ドラッグの軌跡を滑らかな曲線で描く」で扱ったネタだ。ただ、細かい仕込みが違ってくるため、今回は新たに書き下ろす。ライブラリの読込みと初期設定の関数、body要素からの関数呼出しやcanvas要素の配置はいつもどおりだ。
さて、はじめの一歩はつぎのコード1だ。スクリプティングの考え方については、第10回コード1「ドラッグする軌跡でアルファマスクを描く」を参考にしてほしい。この後、コードの組立てをかいつまんで説明しよう。
線はShapeインスタンス(myShape)に描く。ただし、Stageオブジェクト(stage)には直に置かない。先に親となるContainerオブジェクト(container)を加えて、Shapeインスタンスはその入れ子にする。描いた線を後から消すために、Shapeオブジェクトをいくつも加えられるようにしたいからだ。もっとも、今はまだオブジェクトは一つしかつくっていない。
マウスポインタの軌跡を滑らかに描く考え方は、前出第10回「ドラッグの軌跡を滑らかな曲線で描く」と基本的に変わらない。つまり「連続した座標は、すべてコントロールポイントにしてしまう。そして、座標の中点をアンカーポイントとして結ぶ」のだった(第10回図4再掲)。
Ticker.tickイベントのリスナー関数(draw())は、新しいマウスポインタ座標(Stage.mouseXとStage.mouseYプロパティ)と前のマウスポインタ座標(currentPoint)との差をとって、少しでも動きがあれば曲線を描く[1]。そして、ポインタ座標の古い中点(lastMidPoint)と新たな中点(midPoint)を結び、古いマウス座標(lastPoint)がコントロールポイントとなる曲線を描く。そのための関数(drawCurve())は別に設け、ShapeインスタンスのGraphicsオブジェクトと3つの座標のPointオブジェクトを引数に渡した。
さらに、曲線を描く関数(drawCurve())は、マウスの動きの大きさに応じて線幅を変えている。ポインタの古い座標と新しい座標を引数に渡して呼出した関数(setLineThickness())が、線幅(currentLineThickness)を定める。ふたつの座標の距離(distance)を求めて、幅はその値に比例させた。
このコード1に手を加えていく。だが、試してみると目指す表現からはほど遠い。インクフローがおかしくなったペンで描いた線のようだ(図1)。線の太さが滑らかに変わっていない。
値の変化のさせ方を考える
コード1の線幅を滑らかに変えたい。だからといって、マウスの動きに比例させる係数を小さくしたのでは、線幅の差が小さくなってしまう。小さくしたいのは変化であって、線幅ではない。そこで、目標の線幅(lineThickness)と現在の値(currentLineThickness)との差を求め、そのまま加えるのでなく、一定割合(0.25)だけ変える。これで変化が小さくなる。
今回のお題でとくに考えたいことのひとつが、値の変化のさせ方だ。そこで、マウスポインタの軌跡を描く関数(draw())の線の動きも少し変えてみた。新旧ポインタ座標の差(moveXとmoveY)を直ちに線で結ぶのではなく、ここでも一定割合(ease)だけ近づいた線を描く。つまり、マウスポインタの動きに、線が少し遅れてついていく。
これで何とか、マウスポインタの動きに少し遅れて、太さも滑らかに変わる線が描かれるようになる(図2)。
描かれた線を後から消してゆく
つぎは、描かれた線を後から少しずつ消してゆこう。とはいえ、Shapeインスタンスへの描画を一部消すというのは難しい。そこで、Ticker.tickイベントのリスナー関数(draw())は、呼び出されるたびに別のShapeインスタンスをつくって線描することにした。新たなShapeオブジェクトを返す関数(getNewChild())は別に定める。
そして、ステージに置くShapeインスタンスの最大値を予め変数(maxLines)に定めておく。線を描いたShapeインスタンスがその数を超えたら、古い方から順に消してゆく。Shapeオブジェクトを消す関数(removeOldChild())もこの後定める。また、マウスポインタは動かなくても、Shapeオブジェクトを消す関数は、親オブジェクト(container)の子が残りひとつになるまで呼び出し続ける。
Shapeオブジェクトをつくるには、Shape()コンストラクタを呼び出せば済む。関数(getNewChild())を設けるのは、「つくったオブジェクトを使い回す」ためだ(第14回「オブジェクトの使い回しとアニメーション素材の変更」参照)。使い回すオブジェクトを入れる配列はあらかじめ変数(children)に定めた。関数は配列にオブジェクトがあれば取り出し、なければコンストラクタで新たにつくって返す。なお、前に使ったとき描かれた線は、Graphics.clear()メソッドで消しておくのを忘れずに。
Shapeオブジェクトを消す関数(removeOldChild())は、親オブジェクト(container)から一番古い(インデックス0の)オブジェクトを表示リストから除き、配列(children)に加える。
これらの手を加えると、マウスポインタの動きに沿って描かれた線が、後から順に消えてゆくようになる(図3)。以下のコード2に全体をまとめた。今回は、ここまでとしよう。後にjsdo.itのサンプルも掲げた。次回は、線の動きやアニメーションにもう少しこだわろう。