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

第17回 簡単なクラスを定義する

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

コンストラクタのプロトタイプオブジェクトにメソッドを定める

前項のクラスへのメソッドの定め方には,ふたつ問題がある。ひとつは,メソッドを本格的に加え始めると,コンストラクタの中がメソッド設定の羅列で見づらくなる。もうひとつは,メソッドに与えた関数が,メソッドでなく直に関数としても呼べてしまうことだ※1⁠。関数が直に呼ばれると,その中のthis参照はグローバルオブジェクトになってしまうので正しく動かない。

そこで,クラスに備えるメソッドは,コンストラクタ関数のFunction.prototypeプロパティに定める。このプロパティはコンストラクタとして呼出された関数に備わるプロトタイプオブジェクトを参照する。コンストラクタからつくられたインスタンスを参照すると,コンストラクタのthis参照などをとおしてインスタンスに与えられたプロパティやメソッドにアクセスできる。けれど,その中に見当たらないときは,コンストラクタのプロトタイプオブジェクトを探し始め,見つかればインスタンスに定められたのと同じに扱えるのだ。

コンストラクタのFunction.prototypeプロパティにメソッドを定めるときは,名前のない関数で与える。そうすれば,直に関数を呼ばれるおそれがなくなるからだ。

// コンストラクタ関数の定義
function クラス名() {}
// メソッドの定義
クラス名.prototype.メソッド名 = function(引数) {
  // メソッドの処理
}

そこで,前項で定義したコンストラクタ(Point3D())とメソッド(getProjetedPoint())を,つぎのように書き直す。コンストラクタ関数のFunction.prototypeプロパティに定められた関数(getProjetedPoint())は,すべてのインスタンスから参照でき,自身のメソッドとして呼び出せる。

function Point3D(x, y, z) {
  this.x = x;
  this.y = y;
  this.z = z;
  // this.getProjetedPoint = getProjetedPoint;
}
// function getProjetedPoint(focalLength) {
Point3D.prototype.getProjetedPoint = function(focalLength) {
  var point2D = new createjs.Point();
  var w = focalLength / (focalLength + this.z);
  point2D.x = this.x * w;
  point2D.y = this.y * w;
  return point2D;
};

メソッド(getProjetedPoint())をひとつだけもつクラス(Point3D)がこれで定義できた。ここでひとつ,大切な注意がある。メソッドは名前のない関数を代入して定めている。そのため,このコンストラクタを呼び出すステートメントは,代入文の後に書かなければメソッドが備わらない。つまり,クラス定義はJavaScriptコードの冒頭に置くのがよい。そうしてまとめたスクリプト全体がつぎのコード2だ。

コード2 冒頭に3次元座標のクラスと透視投影のメソッドを定義したy軸で星形が回るアニメーション

function Point3D(x, y, z) {
  this.x = x;
  this.y = y;
  this.z = z;
}
Point3D.prototype.getProjetedPoint = function(focalLength) {
  var point2D = new createjs.Point();
  var w = focalLength / (focalLength + this.z);
  point2D.x = this.x * w;
  point2D.y = this.y * w;
  return point2D;
};
var stage;
var drawGraphics;
var points;
var angle = Math.PI / 36;
var matrix = new createjs.Matrix2D();
var stageCenterX;
var _point = new createjs.Point();
var points2D = [];
var focalLength = 300;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  stageCenterX = canvasElement.width / 2;
  drawGraphics = createGraphics(stageCenterX, canvasElement.height / 2);
  points = createStarPoints(5, 65, 25);
  draw(points);
  createjs.Ticker.addEventListener("tick", rotate);
  stage.addEventListener("stagemousemove", setAngle);
}
function setAngle(eventObject) {
  var mouseX = eventObject.stageX;
  angle = (mouseX - stageCenterX) * 1 / 300;
}
function rotate(eventObject) {
  var count = points.length;
  points2D.length = 0;
  matrix.identity().rotate(angle);
  for (var i = 0; i < count; i++) {
    var point = points[i];
    matrix.transformPoint(point.x, point.z, _point);
    point.x = _point.x;
    point.z = _point.y;
    points2D[i] = point.getProjetedPoint(focalLength);
  }
  draw(points2D);
}
function createGraphics(x, y) {
  var drawShape = new createjs.Shape();
  drawShape.x = x;
  drawShape.y = y;
  stage.addChild(drawShape);
  return drawShape.graphics;
}
function draw(points) {
  var count = points.length;
  var point = points[count - 1];
  drawGraphics.clear()
  .beginStroke("mediumblue")
  .setStrokeStyle(3)
  .moveTo(point.x, point.y);
  for (var i = 0; i < count; i++) {
    point = points[i];
    drawGraphics.lineTo(point.x, point.y);
  }
  stage.update();
}
function createStarPoints(numVertices, longRadius, shortRadius) {
  var starPoints = [];
  var angle = Math.PI;
  var theta = angle / numVertices;
  angle /= -2;
  for (var i = 0; i < numVertices; i++) {
    starPoints.push(new Point3D(longRadius * Math.cos(angle), longRadius * Math.sin(angle), 0));
    angle += theta;
    starPoints.push(new Point3D(shortRadius * Math.cos(angle), shortRadius * Math.sin(angle), 0));
    angle += theta;
  }
  return starPoints;
}

このお題は,これででき上がりとする。jsdo.itにコードを掲げた。次回はお題を変えて,クラス定義と透視投影にもう少し取り組んでみたい。このようなサンプルを予定している(なお,このサンプルにはインタラクションはない⁠⁠。

※1
関数を外に定義せず,コンストラクタの中で名前のない関数としてプロパティに定めれば,直に呼ばれることは避けられる。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書