サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

EaselJSのマウスクリックとドラッグ&ドロップ

ID: FN1203005 Technique: HTML5 and JavaScript

EaselJSを使ったマウスイベントの扱い方について基本的なご説明をします。具体的には、マウスクリックとドラッグ&ドロップのふたつのお題でJavaScriptのコードを書いてみましょう。


01 円のShapeインスタンスをふたつつくる
マウスイベントのスクリプティングに入る前に、クリックの対象となるShapeの円をふたつつくります。Shapeを使った円の描き方は「EaselJSで図形を描く」で解説しましたので、その復習です。詳しくは、このノートをお読みください。

HTMLドキュメントの<body>要素には<canvas>要素("myCanvas")が記述され、描画のためのJavaScriptの関数(xInitialize())を呼出しているものとします。

<body onload="xInitialize()">
  <canvas id="myCanvas" width="240" height="180"></canvas>
</body>

円の描かれたShapeインスタンスふたつをCanvasに置くスクリプトは、以下のコード001のとおりです(図001)。円のShapeインスタンスのつくり方(xDrawCircle())は、前出「EaselJSで図形を描く」のコード002「EaselJSでCanvasに円を描く」とほぼ同じです。関数名や引数、ステートメントの位置などが少し変わっただけです。初期化の関数(xInitialize())の中で、forループの繰返し処理によりふたつの円のShapeインスタンスをつくり、異なる位置に置きました(第13〜17行目)[*1]

図001■円を描いたShapeインスタンスふたつがCanvasに置かれる
図001

コード001■EaselJSでCanvasにふたつの円のShapeインスタンスを置く
  1. <script src="easeljs/utils/UID.js"></script>
  2. <script src="easeljs/geom/Matrix2D.js"></script>
  3. <script src="easeljs/display/DisplayObject.js"></script>
  4. <script src="easeljs/display/Container.js"></script>
  5. <script src="easeljs/display/Stage.js"></script>
  6. <script src="easeljs/display/Graphics.js"></script>
  7. <script src="easeljs/display/Shape.js"></script>
  8. <script type="text/javascript">
  9.   var stage;
  10.   function xInitialize() {
  11.     var canvasObject = document.getElementById("myCanvas");
  12.     stage = new Stage(canvasObject);
  13.     for (var i = 0; i < 2; i++) {
  14.       var myInstance = xDrawCircle(20);
  15.       myInstance.x = 50 * (i + 1);
  16.       myInstance.y = 50;
  17.     }
  18.     stage.update();
  19.   }
  20.   function xDrawCircle(nRadius) {
  21.     var myShape = new Shape();
  22.     var myGraphics = myShape.graphics;
  23.     stage.addChild(myShape);
  24.     myGraphics.beginStroke("#0000FF");
  25.     myGraphics.beginFill("#00FFFF");
  26.     myGraphics.drawCircle(0, 0, nRadius);
  27.     return myShape;
  28.   }
  29. </script>

[*1] for文による繰返し処理については、AjaxTower「for文」が参考になるでしょう。


02 インスタンスをマウスクリックで動かす
ふたつの円のShapeインスタンスそれぞれを、マウスクリックで動かしてみます。インスタンスへのクリックは、DisplayObject.onClickイベントで扱います。イベントにハンドラ(コールバック関数)を定めると、インスタンスをクリックしたときにその関数が呼出されます。

DisplayObjectインスタンス.onClick = コールバック関数;
function コールバック関数(MouseEventオブジェクト) {
  // 処理内容
}

また、イベントハンドラは名前のない関数にして、直接イベントに代入することもできます。

DisplayObjectインスタンス.onClick = function(MouseEventオブジェクト) {
  // 処理内容
}

呼出されたイベントハンドラは、引数にMouseEventオブジェクトを受取ります。そのMouseEvent.targetプロパティから、クリックしたインスタンスの参照が得られます。すると、前掲コード001につぎのような手を加えれば、円のShapeインスタンスをクリックするたびに右に10ピクセル動かすことができます(図002)。各ステートメントに添えた番号は、後に掲げる<script>属性全体のコード002における行を示します。

  1. <script src="easeljs/events/MouseEvent.js"></script>
  1. <script type="text/javascript">
  1.   function xInitialize() {
  1.     for (var i = 0; i < 2; i++) {
  1.       myInstance.onClick = clickHandler;
  2.     }
  1.   }
  1.   function clickHandler(eventObject) {
  2.     var instance = eventObject.target;
  3.     instance.x += 10;
  4.     stage.update();
  5.   }
  6. </script>

図002■クリックした円のShapeインスタンスが右に10ピクセル動く
図002

forステートメントの中で、各インスタンスのDisplayObject.onClickイベントにハンドラ(clickHandler())を設定しています(コード002第18行目)。ハンドラに定めた関数は、引数のMouseEventオブジェクトのMouseEvent.targetプロパティからクリックしたインスタンスの参照を得て(第32行目)、そのx座標を10ピクセル右に動かします(第33行目)。最後にCanvasを描き直しますので、Stage.update()メソッドは忘れずに呼出します(第34行目)。

コード002■クリックした円のShapeインスタンスを右に10ピクセル動かす
  1. <script src="easeljs/utils/UID.js"></script>
  2. <script src="easeljs/events/MouseEvent.js"></script>
  3. <script src="easeljs/geom/Matrix2D.js"></script>
  4. <script src="easeljs/display/DisplayObject.js"></script>
  5. <script src="easeljs/display/Container.js"></script>
  6. <script src="easeljs/display/Stage.js"></script>
  7. <script src="easeljs/display/Graphics.js"></script>
  8. <script src="easeljs/display/Shape.js"></script>
  9. <script type="text/javascript">
  10.   var stage;
  11.   function xInitialize() {
  12.     var canvasObject = document.getElementById("myCanvas");
  13.     stage = new Stage(canvasObject);
  14.     for (var i = 0; i < 2; i++) {
  15.       var myInstance = xDrawCircle(20);
  16.       myInstance.x = 50 * (i + 1);
  17.       myInstance.y = 50;
  18.       myInstance.onClick = clickHandler;
  19.     }
  20.     stage.update();
  21.   }
  22.   function xDrawCircle(nRadius) {
  23.     var myShape = new Shape();
  24.     var myGraphics = myShape.graphics;
  25.     stage.addChild(myShape);
  26.     myGraphics.beginStroke("#0000FF");
  27.     myGraphics.beginFill("#00FFFF");
  28.     myGraphics.drawCircle(0, 0, nRadius);
  29.     return myShape;
  30.   }
  31.   function clickHandler(eventObject) {
  32.     var instance = eventObject.target;
  33.     instance.x += 10;
  34.     stage.update();
  35.   }
  36. </script>

03 インスタンスのドラッグ&ドロップ
つぎのお題は、インスタンスのドラッグ&ドロップです。これは、3つのイベントを組合わせて行います。(1)インスタンスのうえでマウスボタンを押したとき、ドラッグを始めます。(2)マウスのボタンをプレスしたまま動かすと、インスタンスの位置をポインタに追随させます。(3)マウスボタンを放したら、ドラッグが終わります。

それぞれを扱うマウスイベントは、DisplayObject.onPressMouseEvent.onMouseMoveおよびMouseEvent.onMouseUpです(表001)。気をつけていただきたいのは、イベントが属するクラスです。イベントDisplayObject.onPressは、DisplayObject.onClickと同じく、クリックの対象となるインスタンスにイベントハンドラを定めます。ところが、後のふたつは、MouseEventクラスのイベントです。

MouseEvent.onMouseMoveMouseEvent.onMouseUpイベントのハンドラは、MouseEventオブジェクトに設定しなければなりません。具体的には、DisplayObject.onPressイベントハンドラ(コールバック関数)が引数に受取ったMouseEventオブジェクトに、MouseEvent.onMouseMoveあるいはMouseEvent.onMouseUpイベントのハンドラを設定します。

表001■ドラッグ&ドロップに用いるマウスイベント
マウスイベント マウス操作 クラス
onPress マウスボタンを押す DisplayObject
onMouseMove マウスを動かす MouseEvent
onMouseUp マウスボタンを放す MouseEvent

それでは、前掲コード002に手を入れるかたちで、ドラッグ&ドロップできるようにします。できあがりは、コード003として後に掲げました。修正する部分は、つぎに抜出したコードのとおりです。各ステートメントの頭の番号は、後掲コード003の行を示します。

  1. function xInitialize() {
        // myInstance.onClick = clickHandler;
  1.     myInstance.onPress = pressHandler;
  2. }
  • /*
  • function clickHandler(eventObject) {
  •   var instance = eventObject.target;
  •   instance.x += 10;
  •   stage.update();
  • }
  • */
  1. function pressHandler(eventObject) {
  2.   var instance = eventObject.target;
  1.   eventObject.instance = instance;
  2.   eventObject.onMouseMove = mouseMoveHandler;
  3.   eventObject.onMouseUp = mouseUpHandler;
  4. }
  5. function mouseMoveHandler(eventObject) {
  6.   var target = this.instance;
  1.   target.x = eventObject.stageX;
  2.   target.y = eventObject.stageY;
  3.   stage.update();
  4. }
  5. function mouseUpHandler(eventObject) {
  6.   this.onMouseMove = this.onMouseUp = null;
  7. }

まず、初期設定の関数(xInitialize())で、Shapeインスタンス(myInstance)に設定するマウスイベントを、DisplayObject.onClickからDisplayObject.onPressに変えました(コード003第19行目)。したがって、DisplayObject.onClickイベントのハンドラ(clickHandler())はそっくり除きます。

つぎに、DisplayObject.onPressイベントのハンドラ(pressHandler())を定めます。関数が引数に受取ったMouseEventオブジェクト(eventObject)に、MouseEvent.onMouseMoveMouseEvent.onMouseUpのイベントハンドラをそれぞれ設定しました(コード003第36〜37行目)。

ここで、気をつけなければならないことがあります。MouseEvent.onMouseMoveイベントのハンドラ(mouseMoveHandler())では、Shapeインスタンスの座標をマウスポインタの位置に合わせて動かすつもりです。ところが、このイベントは、MouseEventクラスに属していました。そのため、ハンドラの引数からMouseEvent.targetプロパティを参照しても、クリックしたShapeインスタンスが得られません[*2]。そこで、DisplayObject.onPressイベントのハンドラ(pressHandler())の中でShapeインスタンスの参照(instance)をMouseEventオブジェクト(eventObject)の変数(instance)に設定しているのです(第33および第35行目)。

そして、このコード003の主題となるMouseEvent.onMouseMoveイベントのハンドラ(mouseMoveHandler())です。早速、MouseEventオブジェクトに変数として設定したShapeインスタンスの参照を取出します。ただし、この関数が受取った引数のMouseEventオブジェクトは、ハンドラを設定したオブジェクト(第35行目)とは別物です。ハンドラが設定されたオブジェクトは、thisキーワードで参照します(第40行目)[*3]

また、引数に受取ったMouseEventオブジェクトのMouseEvent.stageXMouseEvent.stageYプロパティでマウスポインタのxy座標が調べられます。そこで、マウスクリックしたShapeインスタンス(target)の位置をその座標に合わせています(コード003第42〜43行目)。

最後に、MouseEvent.onMouseUpイベントのハンドラ(mouseUpHandler())でドラッグを終えます。そのために、イベントMouseEvent.onMouseMoveMouseEvent.onMouseUpにともにnullを代入して、ハンドラを消し去ります(コード003第47行目)。

これで、ふたつの円のShapeインスタンスを、それぞれドラッグすることができます。ただ、動きがひとつ気になります。それは、インスタンスの端をクリックしても、ドラッグし始めるとマウスポインタがインスタンスの中心にきてしまうことです(図003)。これは、イベントハンドラ(mouseMoveHandler())が、インスタンスの座標にマウスポインタの座標をそのまま代入しているからです(コード003第42〜43行目)。

図003■ドラッグするとマウスポインタがインスタンスの中心にくる
図003左   図003右

以下のコード003では、この問題を直しました。まず、DisplayObject.onPressのイベントハンドラ(pressHandler())が、インスタンス(instance)とクリックしたマウスポインタの座標の差をとり、Pointオブジェクトとしてインスタンスの変数(offset)に設定しています(第4および第34行目)。そして、MouseEvent.onMouseMoveイベントのハンドラ(mouseMoveHandler())で、その座標の差が加わった位置にインスタンスを動かしているのです(第42〜43行目)。これで、初めにクリックしたマウスポインタの位置を保ったまま、インスタンスがドラッグできます(図004)。

図004■クリックしたマウスポインタの位置を保ちつつインスタンスがドラッグできる
図004

コード003■円のShapeインスタンスをドラッグする
  1. <script src="easeljs/utils/UID.js"></script>
  2. <script src="easeljs/events/MouseEvent.js"></script>
  3. <script src="easeljs/geom/Matrix2D.js"></script>
  4. <script src="easeljs/geom/Point.js"></script>
  5. <script src="easeljs/display/DisplayObject.js"></script>
  6. <script src="easeljs/display/Container.js"></script>
  7. <script src="easeljs/display/Stage.js"></script>
  8. <script src="easeljs/display/Graphics.js"></script>
  9. <script src="easeljs/display/Shape.js"></script>
  10. <script type="text/javascript">
  11.   var stage;
  12.   function xInitialize() {
  13.     var canvasObject = document.getElementById("myCanvas");
  14.     stage = new Stage(canvasObject);
  15.     for (var i = 0; i < 2; i++) {
  16.       var myInstance = xDrawCircle(20);
  17.       myInstance.x = 50 * (i + 1);
  18.       myInstance.y = 50;
  19.       myInstance.onPress = pressHandler;
  20.     }
  21.     stage.update();
  22.   }
  23.   function xDrawCircle(nRadius) {
  24.     var myShape = new Shape();
  25.     var myGraphics = myShape.graphics;
  26.     stage.addChild(myShape);
  27.     myGraphics.beginStroke("#0000FF");
  28.     myGraphics.beginFill("#00FFFF");
  29.     myGraphics.drawCircle(0, 0, nRadius);
  30.     return myShape;
  31.   }
  32.   function pressHandler(eventObject) {
  33.     var instance = eventObject.target;
  34.     instance.offset = new Point(instance.x - eventObject.stageX, instance.y - eventObject.stageY);
  35.     eventObject.instance = instance;
  36.     eventObject.onMouseMove = mouseMoveHandler;
  37.     eventObject.onMouseUp = mouseUpHandler;
  38.   }
  39.   function mouseMoveHandler(eventObject) {
  40.     var target = this.instance;
  41.     var offset = target.offset;
  42.     target.x = eventObject.stageX + offset.x;
  43.     target.y = eventObject.stageY + offset.y;
  44.     stage.update();
  45.   }
  46.   function mouseUpHandler(eventObject) {
  47.     this.onMouseMove = this.onMouseUp = null;
  48.   }
  49. </script>

[*2] MouseEvent.onMouseMoveイベントのハンドラが受取る引数のMouseEventオブジェクトでMouseEvent.targetプロパティを調べると、Stageオブジェクト(stage)の参照になります。

[*3] JavaScriptでは、関数の中のthisキーワードはその関数をもつオブジェクトの参照になります。したがって、イベントハンドラ(mouseMoveHandler())を設定したMouseEventオブジェクト(第35行目)の参照が得られるのです。

ActionScript 3.0や他の言語で、thisキーワードが関数の定義されたオブジェクトを参照するのと異なります。なお、ActionScript 2.0/1.0ではJavaScriptと同じ仕様でした。


04 ハンドラが設定されたオブジェクトをthisキーワードで参照する
前項で「ハンドラが設定されたオブジェクトは、thisキーワードで参照」できると述べました。すると、DisplayObject.onPressイベントのハンドラ(pressHandler())でも、thisキーワードを用いればMouseEvent.targetプロパティは使わずに済みます。つぎのようにステートメントが1行減り、見た目も少しすっきりします。ただ、処理の効率はほとんど変わらないでしょう。

  1. function pressHandler(eventObject) {
  2.   // var instance = eventObject.target;
      // instance.offset = new Point(instance.x - eventObject.stageX, instance.y - eventObject.stageY);
  3.   this.offset = new Point(this.x - eventObject.stageX, this.y - eventObject.stageY);
      // eventObject.instance = instance;
  4.   eventObject.instance = this;
  5.   eventObject.onMouseMove = mouseMoveHandler;
  6.   eventObject.onMouseUp = mouseUpHandler;
  7. }

作成者: 野中文雄
作成日: 2012年3月22日


Copyright © 2001-2012 Fumio Nonaka.  All rights reserved.