サイトトップ

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

HTML5テクニカルノート

EaselJSを使ったインスタンスの傾斜・伸縮・移動

ID: FN1210004 Technique: HTML5 and JavaScript

DisplayObject.skewXおよびDisplayObject.skewYプロパティを使うと、インスタンスを傾斜させることができます。さらに伸縮のDisplayObject.scaleXDisplayObject.scaleYプロパティを組合わせて、画像の角に置いたハンドルをドラッグすると、インスタンスがその位置に合わせて変形するようにしてみましょう(001)[*1]

図001■画像がハンドルをドラッグすると変形する
図001左   図001右

[*1] 傾斜は、インスタンスの矩形の境界領域を平行四辺形に変形します。つまり、向かい合う2辺は平行のままで、台形に変えることはできません。いわゆる「パースペクティブ」はかけられないのです。


01 読込んだ画像の角にハンドルを置く

お題とする変形に取りかかる前に、読込んだ画像の右上角にドラッグできるハンドルをひとつ加えます。書上げたscript要素全体は、後にコード001として掲げます。抜書きして示すステートメントの行番号は、このコード001にもとづきます。

画像のPNGファイル(image.png)はHTMLドキュメントと同じ場所のフォルダに(images)納め、PreloadJSクラスを用いて読込み、Bitmapインスタンスに納めてCanvasの真ん中に置きます(図002)。PreloadJSクラスによる外部画像ファイルの読込み方については、「PreloadJSで外部画像ファイルの読込みを待つ」をお読みください。

  1. var stage;
  2. var myBitmap;
  3. var file = "images/image.png";
  1. function initialize() {
  2.   var canvasElement = document.getElementById("myCanvas");
  3.   stage = new Stage(canvasElement);
  4.   var loader = new PreloadJS(false);
  5.   loader.onFileLoad = draw;
  6.   loader.loadFile(file);
  7.   myBitmap = new Bitmap(file);
  8.   setAppearance(myBitmap, canvasElement.width / 2, canvasElement.height / 2);
  1. }
  2. function setAppearance(instance, nX, nY) {
  3.   instance.x = nX;
  4.   instance.y = nY;
  5.   stage.addChild(instance);
  6. }
  7. function draw(eventObject) {
  8.   var myImage = eventObject.result;
  9.   var imageWidth = myImage.width;
  10.   var imageHeight = myImage.height;
  11.   myBitmap.x -= imageWidth / 2;
  12.   myBitmap.y -= imageHeight / 2;
  1.   stage.update();
  2. }

図002■画像ファイルを読込んでCanvasの真ん中に置く
図002

そして、ドラッグするハンドルのShapeインスタンスひとつに円を描いて、画像が納められたBitmapインスタンスの右上角に置きます(図003)。インスタンスのドラッグのやり方については、「EaselJSのマウスクリックとドラッグ&ドロップ」の03「インスタンスのドラッグ&ドロップ」をお読みください。ハンドルは後で数を3つに増やしますので、配列(handles)に入れて扱うことにしました(第23および33行目)。これで準備は整いましたので、つぎに項を改めてインスタンスの変形にかかります。

  1. var handles = [];
  1. function initialize() {
  1.   handles[0] = createHandle(handleSettings);
  2. }
  1. function createHandle(settings) {
  2.   var myShape = new Shape();
  3.   myShape.onPress = startDrag;
  4.   drawHandle(myShape.graphics, settings);
  5.   return myShape;
  6. }
  7. function drawHandle(myGraphics, settings) {
  8.   myGraphics.beginFill(settings.color);
  9.   myGraphics.drawCircle(0, 0, settings.radius);
  10. }
  11. function startDrag(eventObject) {
  12.   eventObject.instance = this;
  13.   eventObject.onMouseMove = drag;
  14.   eventObject.onMouseUp = stopDrag;
  15.   this.offset = new Point(this.x - eventObject.stageX, this.y - eventObject.stageY);
  16. }
  17. function drag(eventObject) {
  18.   var instance = this.instance;
  19.   var offset = instance.offset;
  20.   instance.x = eventObject.stageX + offset.x;
  21.   instance.y = eventObject.stageY + offset.y;
  1.   stage.update();
  2. }
  3. function stopDrag(eventObject) {
  4.   this.onMouseMove = this.onMouseUp = null;
  5. }

図003■ドラッグするハンドルのインスタンスを画像の右上角に置く
図003


02 インスタンスを傾斜させる

画像の右上角に置いたハンドルで、インスタンスを垂直方向に傾斜させます。この場合には、DisplayObject.skewYプロパティにx軸の正方向からの傾斜角を度数で与えます(図004)。けれども、ドラッグするハンドルの位置は角度でなく座標で定められます。つまり、xy座標から角度を求めなければなりません。

図004■DisplayObject.skewYプロパティでインスタンスを垂直方向に傾斜する
図004

座標から角度を求める関数はMath.atan2()です。引数にxy座標値を与えると、角度がラジアン角で返されます。DisplayObject.skewYプロパティの角度は度数で定めますので、ラジアンは度数に直します[*2]。メソッドに渡す引数の順序はy座標が先であることにもご注意ください。

Math.atan2(y座標, x座標)

求める角度は、インスタンス左上角(point1)からドラッグしたハンドルの位置(point1)を見た値です(第77〜79行目)。インスタンスを変形する関数(transform())は、引数に受取ったふたつの座標の差を求めて、Math.atan2()関数により角度を導きます(第84〜86行目)。そして、ラジアン角を度数に直したうえで、DisplayObject.skewYプロパティに定めます(第87および89行目)。これで、ドラッグしたハンドルに合わせて、インスタンスは傾斜します。

  1. function drag(eventObject) {
  1.   transformBitmap();
  2.   stage.update();
  3. }
  1. function transformBitmap() {
  2.   var handle = handles[0];
  3.   var point0 = new Point(handle.x, handle.y);
  4.   var point1 = new Point(myBitmap.x, myBitmap.y);
  5.   transform(point0, point1);
  6. }
  7. function transform(point0, point1) {
  1.   var point1_0_x = point0.x - point1.x;
  2.   var point1_0_y = point0.y - point1.y;
  3.   var radiansX = Math.atan2(point1_0_y, point1_0_x);
  4.   var angleX = radiansX * RADIANS_TO_DEGREES;
  1.   myBitmap.skewY = angleX;
  1.   stage.update();
  2. }

03 インスタンスを伸縮する

もっとも、インスタンスは伸縮しませんので、ハンドルの位置を近づけたり遠ざけても、傾斜するだけでインスタンスの右上角はハンドルにはついていきません(図02-09-005)。DisplayObject.scaleXプロパティでインスタンスを水平方向に伸縮しましょう。

図005■インスタンスはハンドルに合わせて傾斜しても伸縮しない
図005

DisplayObject.scaleXプロパティには、インスタンスのもとの幅に対する伸縮したい幅の比率を入れます。ただし、もとの幅というのは、詳しく述べるなら水平方向の長さです。つまり、傾斜すると読込んだ画像イメージの幅より短くなります(図006)。

図006■DisplayObject.scaleXプロパティを求めるときのもとの幅は水平方向の長さ
図006左   図006右

画像イメージの幅と傾斜角から水平方向の長さを求めるには、つぎの式のように三角関数のcosを用います[*3]。また、Math.cos()関数の引数に渡すのは、ラジアン値でなければならないことに注意しましょう。

伸縮率 = 伸縮幅 / 水平方向の長さ
= 伸縮幅 / (イメージの幅×cos(傾斜ラジアン角))

インスタンスを変形する関数(transform())には、つぎのように水平方向の伸縮の処理を加えます。上記の式で水平方向の伸縮率を求め、DisplayObject.scaleXプロパティに定めました(第91行目)。ただし、cos値(cosX)が0のときは計算式の分母が0となるため、不適切な計算値が導かれます。そこで、if文でcos値が0でないことを確かめています(第90行目)。

  1. function transform(point0, point1) {
  2.   var point1_0_x = point0.x - point1.x;
  3.   var point1_0_y = point0.y - point1.y;
  4.   var radiansX = Math.atan2(point1_0_y, point1_0_x);
  1.   var cosX = Math.cos(radiansX);
  1.   if (cosX != 0) {
  2.     myBitmap.scaleX = point1_0_x / (myImage.width * cosX);
  3.   }
  4.   stage.update();
  5. }

これで、ハンドルをドラッグすると、その位置に合わせてインスタンスは垂直方向に傾斜し、水平方向に伸縮します。でき上がったscript要素全体は、以下のコード001のとおりです。

図007■ハンドルの位置に合わせてインスタンスが垂直方向に傾斜し水平方向に伸縮する
図007

コード001■ドラッグするハンドルに合わせてインスタンスを垂直方向に傾斜し水平方向に伸縮する
  1. <script>
  2. var createjs = window;
  3. </script>
  4. <script src="easeljs/utils/UID.js"></script>
  5. <script src="easeljs/geom/Matrix2D.js"></script>
  6. <script src="easeljs/geom/Point.js"></script>
  7. <script src="easeljs/events/MouseEvent.js"></script>
  8. <script src="easeljs/display/DisplayObject.js"></script>
  9. <script src="easeljs/display/Container.js"></script>
  10. <script src="easeljs/display/Stage.js"></script>
  11. <script src="easeljs/display/Bitmap.js"></script>
  12. <script src="easeljs/display/Shape.js"></script>
  13. <script src="easeljs/display/Graphics.js"></script>
  14. <script src="preloadjs/AbstractLoader.js"></script>
  15. <script src="preloadjs/PreloadJS.js"></script>
  16. <script src="preloadjs/TagLoader.js"></script>
  17. <script src="preloadjs/XHRLoader.js"></script>
  18. <script>
  19. var stage;
  20. var myBitmap;
  21. var file = "images/image.png";
  22. var handleSettings = {radius:5, color:"blue"};
  23. var handles = [];
  24. var RADIANS_TO_DEGREES = 180 / Math.PI;
  25. function initialize() {
  26.   var canvasElement = document.getElementById("myCanvas");
  27.   stage = new Stage(canvasElement);
  28.   var loader = new PreloadJS(false);
  29.   loader.onFileLoad = draw;
  30.   loader.loadFile(file);
  31.   myBitmap = new Bitmap(file);
  32.   setAppearance(myBitmap, canvasElement.width / 2, canvasElement.height / 2);
  33.   handles[0] = createHandle(handleSettings);
  34. }
  35. function setAppearance(instance, nX, nY) {
  36.   instance.x = nX;
  37.   instance.y = nY;
  38.   stage.addChild(instance);
  39. }
  40. function draw(eventObject) {
  41.   var myImage = eventObject.result;
  42.   var imageWidth = myImage.width;
  43.   var imageHeight = myImage.height;
  44.   myBitmap.x -= imageWidth / 2;
  45.   myBitmap.y -= imageHeight / 2;
  46.   setAppearance(handles[0], myBitmap.x + imageWidth, myBitmap.y);
  47.   stage.update();
  48. }
  49. function createHandle(settings) {
  50.   var myShape = new Shape();
  51.   myShape.onPress = startDrag;
  52.   drawHandle(myShape.graphics, settings);
  53.   return myShape;
  54. }
  55. function drawHandle(myGraphics, settings) {
  56.   myGraphics.beginFill(settings.color);
  57.   myGraphics.drawCircle(0, 0, settings.radius);
  58. }
  59. function startDrag(eventObject) {
  60.   eventObject.instance = this;
  61.   eventObject.onMouseMove = drag;
  62.   eventObject.onMouseUp = stopDrag;
  63.   this.offset = new Point(this.x - eventObject.stageX, this.y - eventObject.stageY);
  64. }
  65. function drag(eventObject) {
  66.   var instance = this.instance;
  67.   var offset = instance.offset;
  68.   instance.x = eventObject.stageX + offset.x;
  69.   instance.y = eventObject.stageY + offset.y;
  70.   transformBitmap();
  71.   stage.update();
  72. }
  73. function stopDrag(eventObject) {
  74.   this.onMouseMove = this.onMouseUp = null;
  75. }
  76. function transformBitmap() {
  77.   var handle = handles[0];
  78.   var point0 = new Point(handle.x, handle.y);
  79.   var point1 = new Point(myBitmap.x, myBitmap.y);
  80.   transform(point0, point1);
  81. }
  82. function transform(point0, point1) {
  83.   var myImage = myBitmap.image;
  84.   var point1_0_x = point0.x - point1.x;
  85.   var point1_0_y = point0.y - point1.y;
  86.   var radiansX = Math.atan2(point1_0_y, point1_0_x);
  87.   var angleX = radiansX * RADIANS_TO_DEGREES;
  88.   var cosX = Math.cos(radiansX);
  89.   myBitmap.skewY = angleX;
  90.   if (cosX != 0) {
  91.     myBitmap.scaleX = point1_0_x / (myImage.width * cosX);
  92.   }
  93.   stage.update();
  94. }
  95. </script>

[*2] ラジアンは角度を半径1の円(「単位円」と呼びます)における弧の長さで表します(図008)。つまり、2πラジアンは360度です。したがって、ラジアンはつぎのようにして、度数に換算できます。

度数 / ラジアン = 360 / 2π = 180 / π
度数 = ラジアン×180 / π

図008■ラジアンは角度を単位円の弧の長さで表す

[*3] 三角関数は単位円について、中心角θのxy座標を(cosθ, sinθ)と定めます(図009)。すると、中心から距離がrで角度θのxy座標は、単純にrを掛合わせて、(r cosθ, r sinθ)になります。

図009■三角関数は単位円における角度θのxy座標を定める


04 3つのハンドルでインスタンスを傾斜・伸縮・移動する

ご参考までに、ハンドルを3つに増やしたサンプルだけご紹介します(図010)。左下角のハンドルの処理は、基本的に右上角の考え方でxy座標を入替えればよいです。右上角のハンドルは、インスタンスの座標を移動します。ただし、他のふたつのハンドルは動きませんので、同時に傾斜と伸縮を行う結果となります。

図010■3つのハンドルをドラッグすると画像が変形する
図010左   図010右

なお、初めに述べたとおり傾斜した矩形領域は台形にはできず、平行四辺形にしかなりません。そのため、インスタンスの3つの角の座標が定まれば、残りの4つ目の位置は決まります。


作成者: 野中文雄
作成日: 2012年10月16日


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