HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う

第23回 マウスポインタの軌跡を滑らかな線で描きながら消す

この記事を読むのに必要な時間:およそ 8 分

つぎなるお題は,マウスポインタの動きに沿って,滑らかに描かれては消える線のアニメーションだ。マウスが大きく動くと,線は太くなる。また,線はマウスポインタの軌跡そのままではなく,動きに少し弾みがついて描かれる。CreateJSコミュニティエバンジェリストのSebastian DeRossi氏がつくられたサンプルSmooth Lineをもとに,表現は少し変えている。

マウスポインタの軌跡を滑らかな曲線で描く

はじめの一歩として,マウスポインタの軌跡を滑らかな曲線で描く。この連載をとおして読んでくださっている読者なら,どこかで聞覚えのある表現だと気づかれたかもしれない。第10回ドラッグの軌跡を滑らかな曲線で描くで扱ったネタだ。ただ,細かい仕込みが違ってくるため,今回は新たに書き下ろす。ライブラリの読込みと初期設定の関数,body要素からの関数呼出しやcanvas要素の配置はいつもどおりだ。

<script src="http://code.createjs.com/easeljs-0.7.1.min.js"></script>
<script>
function initialize() {
  // 初期設定
}
</script>
<body onLoad="initialize()">
  <canvas id="myCanvas" width="240" height="180"></canvas>
</body>

さて,はじめの一歩はつぎのコード1だ。スクリプティングの考え方については,第10回コード1ドラッグする軌跡でアルファマスクを描くを参考にしてほしい。この後,コードの組立てをかいつまんで説明しよう。

コード1 マウスポインタの軌跡を滑らかな曲線で描く

var stage;
var container;
var myShape = new createjs.Shape();
var lastMidPoint = new createjs.Point();
var currentPoint = new createjs.Point();
var lastPoint = new createjs.Point();
var currentLineThickness = 1;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  container = new createjs.Container();  
  stage.addChild(container);
  container.addChild(myShape);
  lastPoint.x = lastMidPoint.x = canvasElement.width / 2;
  lastPoint.y = lastMidPoint.y = canvasElement.height / 2;
  createjs.Ticker.addEventListener("tick", draw);   
}
function draw() {
  var moveX = (stage.mouseX - currentPoint.x);
  var moveY = (stage.mouseY - currentPoint.y);
  if (moveX * moveX + moveY * moveY > 0.1) {
    currentPoint.x += moveX;
    currentPoint.y += moveY;
    var midPoint = new createjs.Point((lastPoint.x + currentPoint.x) / 2,  (lastPoint.y + currentPoint.y) / 2);
    drawCurve(myShape.graphics, lastMidPoint, midPoint, lastPoint);
    lastPoint.initialize(currentPoint.x, currentPoint.y);
    lastMidPoint.initialize(midPoint.x, midPoint.y);
  }
  stage.update();
}
function drawCurve(myGraphics, oldPoint, newPoint, controlPoint) {
  setLineThickness(oldPoint, newPoint);
  myGraphics.beginStroke("black")
  .setStrokeStyle(currentLineThickness, "round", "round")
  .moveTo(oldPoint.x, oldPoint.y)
  .quadraticCurveTo(controlPoint.x, controlPoint.y, newPoint.x, newPoint.y);   
}
function setLineThickness(oldPoint, newPoint) {
  var distanceX = newPoint.x - oldPoint.x;
  var distanceY = newPoint.y - oldPoint.y;
  var distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
  var lineThickness = distance * 0.2;
  currentLineThickness = lineThickness;
}

線はShapeインスタンス(myShape)に描く。ただし,Stageオブジェクト(stage)には直に置かない。先に親となるContainerオブジェクト(container)を加えて,Shapeインスタンスはその入れ子にする。描いた線を後から消すために,Shapeオブジェクトをいくつも加えられるようにしたいからだ。もっとも,今はまだオブジェクトは一つしかつくっていない。

var stage;
var container;
var myShape = new createjs.Shape();

function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  container = new createjs.Container();  
  stage.addChild(container);
  container.addChild(myShape);

}

マウスポインタの軌跡を滑らかに描く考え方は,前出第10回「ドラッグの軌跡を滑らかな曲線で描く」と基本的に変わらない。つまり連続した座標は,すべてコントロールポイントにしてしまう。そして,座標の中点をアンカーポイントとして結ぶのだった第10回図4再掲)。

第10回 図4 座標はコントロールポイントとして中点をアンカーポイントに定める(再掲)

第10回 図4 座標はコントロールポイントとして中点をアンカーポイントに定める(再掲)

Ticker.tickイベントのリスナー関数(draw())は,新しいマウスポインタ座標(Stage.mouseXとStage.mouseYプロパティ)と前のマウスポインタ座標(currentPoint)との差をとって,少しでも動きがあれば曲線を描く※1)。そして,ポインタ座標の古い中点(lastMidPoint)と新たな中点(midPoint)を結び,古いマウス座標(lastPoint)がコントロールポイントとなる曲線を描く。そのための関数(drawCurve())は別に設け,ShapeインスタンスのGraphicsオブジェクトと3つの座標のPointオブジェクトを引数に渡した。

var lastMidPoint = new createjs.Point();
var currentPoint = new createjs.Point();
var lastPoint = new createjs.Point();
var currentLineThickness = 1;
function initialize() {

  lastPoint.x = lastMidPoint.x = canvasElement.width / 2;
  lastPoint.y = lastMidPoint.y = canvasElement.height / 2;

  createjs.Ticker.addEventListener("tick", draw);   
}

function draw() {
  var moveX = (stage.mouseX - currentPoint.x);
  var moveY = (stage.mouseY - currentPoint.y);
  if (moveX * moveX + moveY * moveY > 0.1) {{
    currentPoint.x += moveX;
    currentPoint.y += moveY;
    var midPoint = new createjs.Point((lastPoint.x + currentPoint.x) / 2,  (lastPoint.y + currentPoint.y) / 2);
    drawCurve(myShape.graphics, lastMidPoint, midPoint, lastPoint);
    lastPoint.initialize(currentPoint.x, currentPoint.y);
    lastMidPoint.initialize(midPoint.x, midPoint.y);
  }
  stage.update();
}
function drawCurve(myGraphics, oldPoint, newPoint, controlPoint) {

  myGraphics.beginStroke("black")
  .setStrokeStyle(currentLineThickness, "round", "round")
  .moveTo(oldPoint.x, oldPoint.y)
  .quadraticCurveTo(controlPoint.x, controlPoint.y, newPoint.x, newPoint.y);   
}

さらに,曲線を描く関数(drawCurve())は,マウスの動きの大きさに応じて線幅を変えている。ポインタの古い座標と新しい座標を引数に渡して呼出した関数(setLineThickness())が,線幅(currentLineThickness)を定める。ふたつの座標の距離(distance)を求めて,幅はその値に比例させた。

function drawCurve(myGraphics, oldPoint, newPoint, controlPoint) {
  setLineThickness(oldPoint, newPoint);

}
function setLineThickness(oldPoint, newPoint) {
  var distanceX = newPoint.x - oldPoint.x;
  var distanceY = newPoint.y - oldPoint.y;
  var distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
  var lineThickness = distance * 0.2;
  currentLineThickness = lineThickness;
}

このコード1に手を加えていく。だが,試してみると目指す表現からはほど遠い。インクフローがおかしくなったペンで描いた線のようだ図1)。線の太さが滑らかに変わっていない。

図1 マウスポインタの動きに沿って描いた線の太さが滑らかに変わらない

図1 マウスポインタの動きに沿って描いた線の太さが滑らかに変わらない

※1
マウスが動いたかどうかは,新旧ポインタ座標の距離が0かを調べればわかる。もっとも,距離の値そのものは使わないので,平方根を求める手間は省き,距離の2乗のまま用いた。また,誤差が少し出ることもあるので,小さな小数値(0.1)と比べている。
なお,2乗はMath.pow()メソッドを使うより,変数値を2回掛けた方が速い(第7回「粒子同士が引き合う力を直線の濃淡で示す」の「オブジェクトが引合う力の範囲をかぎるの項参照)。

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入