Logical Thinking and Programming in 2012 Spring
Minohara Class


第12回 画像、アニメーション、入力

画像のキャンバスへの表示

画像として指定できるのは、JPEG形式、PNG形式、GIF形式の画像ファイルです。PNGやGIF形式では、背景を透明にした画像を作れます。

var 変数 = new Image( ); img.src = "画像のファイル名";

たとえば、以下の記述は、「sample.jpg」という画像ファイルを読み込んでいます。

var img = new Image( );
img.src = "sample.jpg";

画像の読込みは、上記のような形で行ないますが、なるべく早いうちに読込みだけをしておかないと、表示が間に合いません。キャンバスへの表示は、以下のようにします。

var canvas = document.getElementById( "canvas" );
var ctx = canvas.getContext( "2d" );
ctx.drawImage( img, 0, 0 );

drawImageの後の2つの座標は、x, y座標です。座標の左上を指定します。また、次のように高さや幅を指定することもできます。

ctx.drawImage( img, 0, 0, 200, 100 );

読み込んできた画像のサイズ(幅と高さ)がわからないかも知れません。その場合は、widthとheightの属性で獲得することができます。以下の記述は、幅と高さが元の画像の半分(画像全体では1/4)の大きさで表示するものです。

var width = img.width;
var height = img.height;
ctx.drawImage( img, 0, 0, widht/2, height/2 );

drawImageは、最初に描いた方が、より背景に表示されます。たとえば、キャンバス全面に背景をあしらう場合は、背景には、JPEGの画像がよいでしょう。その手前に、背景を抜いたPNG形式の画像を貼り付けます。

たとえば、次のような画像があるとしましょう。


背景の空のback.jpg

前面に表示するbird.png

この2つを読み込んで、鳥を手前に表示するスクリプトは次のようになります。

var sky = new Image(  );
sky.src = "back.jpg";
var bird = new Image(  );
bird.src = "bird.png";

function draw( ) {
	var canvas = document.getElementById('canvas');
	var ctx = canvas.getContext('2d');
	ctx.drawImage( sky, 0, 0 );
	ctx.drawImage( bird, 64, 64 );
}

このdraw関数は、bodyタグなどにonload="draw()"という形で埋め込んで、Webページのロードが終わった後で、実行してあげます。実際に、そのような設定をして表示させてみると、以下のようになります。

サウンド

サウンドは、画像と同じような形でロードします。サウンドで再生可能なファイル形式は、Webブラウザによって異なります。また、ブラウザでもプラグインがインストールされているかによっても異なります。

au形式wave形式mp3形式aac形式ogg形式
Safari 5.1×
Firefox 11×××
Opera 11×××
Chrome 18
var  sound = new Audio( "サウンドファイル" );
sound.play( ); // 1回だけ再生
sound.pause( ); // 再生を止める

Audioクラスは、次のようなプロパティを持っています。よく使う3つだけ記しておきます。詳しくは、 HTMLMediaElementを参照して下さい。

プロパティ名値のタイプ意味
currentTime実数現在再生している時間
duration実数再生時間(参照のみ)
loop論理値繰り返し、再生するかどうか

以下のリンクは、普通にクリックしたらWebブラウザのプラグインなどで音声を鳴らしてくれます。

piano.oggファイル

jazz.wavファイル

spacemusic.auファイル

上記のリンクを右クリック(あるいはControl+左クリック)でダウンロードしてみて下さい。 どのブラウザ上のJavaScriptでも再生されそうな jazz.wavファイルについて、ボタンが押されたら鳴るように、HTMLとJavaScriptのスクリプトを記述してみます。Playボタンで再生され、Pauseボタンで止まります。Resetボタンで再生位置を最初に戻します。

<input type="button" value="Play" onclick="jazz.play( )" />
<input type="button" value="Pause" onclick="jazz.pause( )" />
<input type="button" value="Reset" onclick="jazz.currentTime =0;" />
</p>
<script language="javascript">
// audioファイルの読込み
var jazz = new Audio( "jazz.wav" );
jazz.loop = true;
</script>

アニメーション

アニメーションをするためには、定期的に関数を呼び出す必要があります。この設定をする関数(高階関数)に次のようなものがあります。

var 変数 = setInterval( 関数名, ミリ秒 );
clearInterval( 変数名 );

setIntervalは、指定されたミリ秒間隔で関数を呼び出します。このときに、定期的に呼び出される関数の識別子が返されますので、それを変数に代入します。clearIntervalは、変数に代入されたその識別子を使って関数を定期的に呼び出すのを止めます。

たとえば、次の記述は、1秒間隔でHTMLのタグで、idが'datearea'で指定された場所に、時刻を定期的に表示する関数の記述と、その呼出しの設定になっています。

var timer = setInterval( updateDate, 1000 );

function updateDate( ) {
	var datearea = document.getElementById('datearea');
	datearea.innerHTML = (new Date( )).toLocaleString( );
}

実際に実行させると以下のような感じです。

日時

キャンバス上でアニメーションを行なうには、この高階関数を組み込みます。 たとえば、ボタンを押したらアニメーションが始まるようにする場合は、 アニメーションを始めさせる関数と終わらせる関数を用意して次のように します。

var animetask = null;

function controlAnimation( ) {
	var button = document.getElementById('button');
	if ( animetask != null ) {  // 既に動いていたら、止める
		clearInterval( animetask );
		animetask = null;
		button.value = "Start";
	} else { // そうでなければ、開始させる
		animetask = setInterval( updateAnime, 100 );
		button.value = "Stop";
	}
}

これにボタンを用意してあげます。

	<input id="button" type="button" value="Start" onclick="controlAnimation()">

実際にアニメーションをする関数を以下のような感じで記述できます。鳥を表示する座標を刻々と変化させて、動いているように表示します。

var birdx = 100;

function updateAnime( ) {
	var canvas = document.getElementById('canvas2');
	var ctx = canvas.getContext('2d');
	ctx.clearRect( birdx, 20, 64, 64 );
	birdx = birdx - 10;
	if ( birdx < -64 ) { birdx = 500; }
	ctx.drawImage( bird, birdx, 20, 64, 64 );
}

アニメーションをするときは、一度clearRectで消さないと、その上に描画してしまいます。ということで、 clearRectを使って消しています。左に隠れたら、また右側から出てくるようにbirdxの値を設定して います。 実際に、実行させると次のようになります。

アニメーション

キャンバスなどでの入力

キャンバスに対して次のような入力に対して、対処する関数を呼び出すようにcanvasタグに指定しておきます。

マウス onclick クリックした時
ondblclick ダブルクリックした時
onmousedown マウスボタンを押した時
onmouseup マウスボタンを放した時
onmouseover マウスが領域と重なった時
onmouseout マウスが領域から出た時
onmousemove マウスが移動している時
キー onkeydown キーが押し下げられた時
onkeypress キー入力があった時
onkeyup 押し下げられたキーが放された時

たとえば、以下の記述は、マウスが押されたときに、mouseHandler関数を呼び出すような設定を記述しています。実パラメータとして、ウィンドウのeventという属性名を指定し、起こったイベントの情報を渡しています。

	<canvas id="canvas3" width=200 height=200 onmousedown="mouseHandler(event)">
	</canvas>

canvasタグでなくても構わないのですが、このようにHTMLの一定の要素に対して、イベントハンドラ(イベントに対処する関数)を割り当てることにより、マウス入力やキー入力を直接得ることができます。

マウス入力

onclickやondblclickと、onmousedownの違いは、マウスボタンの押された状態を直接見るのか、それとも押されて、離された段階で見るのかの違いになってきます。onclickやondblclickは、マウスボタンが押されて離されるまで起こってから(ondblclickは、それが2回生じてから)、イベントハンドラが呼び出されます。それに対して、onmousedownは、マウスボタンが押された段階で呼び出されます。

マウスの場合は、受け取ったイベントから、clientXおよびclientYという属性を用いて、そのイベントが起こったキャンバス上の座標を得ることができます。しかしながら、このclientXとclientYという属性は、ウィンドウ上の座標しか返してくれません。そのため、イベントを起こした対象(target)から、その枠の四角形の領域の座標を取得して、x座標に関しては、その左端の座標を引く、y座標に関しては、その上端の座標を引いてあげる必要があります。四角形の領域を取得するのには、getBoundingClientRect( )関数を用います。

たとえば、以下のmouseHandler関数は、キャンバスを対象に、そのキャンバス内での座標をmx, myに読み取っています。そこを中心に小さな四角形を表示するようにしています。

function mouseHandler( event ) {
	var rect = event.target.getBoundingClientRect( );

	var mx = event.clientX - rect.left ;
	var my = event.clientY - rect.top ;
	
	var canvas = document.getElementById('canvas3');
	var ctx = canvas.getContext('2d');
	ctx.strokeRect( mx-5, my-5, 10, 10 );
}

実際に動かしてみましょう。以下のキャンバス領域でマウスを押してみて下さい。

clientX, clientY以外に、ページ上でのx, y座標を得るpageX, pageYや、レイヤ上でのx, y座標を得るlayerX, layerY、あるいはスクリーン上でのx, y座標を得る、screenX, screenYなどの属性がイベントには用意されています。クリックした要素上の座標を求めるoffsetX, offsetYもあるのですが、ブラウザによって動かない場合がありますので、どのような環境でも動くスクリプトを記述するために、上記のような形で座標を求めました。

マウスボタンの種類を見たい場合、buttonという属性がイベントには用意されています。この属性の値は、1 は左ボタン、2 は右ボタン、4 は真中のボタンを示し、2 つ以上のボタンが同時に押されていた場合はその合計値となります。

マウスのドラッグについて

マウスがドラッグされたときは、onmousedownイベントとonmouseupイベントと、onmousemoveイベントを利用します。最初に押されたときに、論理値を持つ変数に押されたことを記録します(たとえば、trueにします)。マウスが離されたときは、この変数に離されたことを記録します(たとえば、falseにします)。この変数がtrueの間だけ、onmousemoveイベントで呼び出される関数で何かの処理をします。

先ほどのマウスがクリックされた例題を拡張して、マウスが押されてドラッグされている間も、四角が表示されるようにしてみましょう。まず、次のように、それぞれのイベントで呼び出す関数を指定します。

	<canvas id="canvas4" width=400 height=200
		onmousedown="mouseStart(event)"
		onmousemove="mouseDraw(event)"
		onmouseup="mouseEnd(event)" >
	</canvas>

それぞれに対応する関数を定義します。mouseDownedという変数に、マウスが押された場合はtrueを、マウスが離された場合はfalseを代入しています。mouseDraw関数では、この変数の値を見て描画するかどうか判断しています。それ以外は、前出の記述とほぼ同じです。

var  mouseDowned = false;

function mouseStart( event ) {
	mouseDowned = true;
}

function mouseEnd( event ) {
	mouseDowned = false;
}

function mouseDraw( event ) {
	if ( mouseDowned ) {
		var rect = event.target.getBoundingClientRect( );

		var mx = event.clientX - rect.left ;
		var my = event.clientY - rect.top ;
	
		var canvas = document.getElementById('canvas4');
		var ctx = canvas.getContext('2d');
		ctx.strokeRect( mx-5, my-5, 10, 10 );
	}
}

それでは、実際に使ってみましょう。以下のキャンバス領域で、マウスをドラッグしてみて下さい。

キーボード入力

キーボード入力は、documentオブジェクトに、onkeydown、onkeyup、onkeypressのイベント呼び出される関数を割り付けていきます。onkeypressは、onkeydownとonkeyupが生じて初めて発生するイベントです。たとえば、大文字のAを入力するのには、Shiftキーを押して、Aのキーを押す必要があります。そのときに、onkeypressでは、文字として大文字のAのキーコードを得ることができます。マウスのonclickとonmousedownの違いと似ています。

キーボードハンドラの関数を割り付けるのは、タグで行なうのではなくて、スクリプト上で次のように、documentの属性として代入して設定します。

document.onkeydown=キーが押されたときの関数名;
document.onkeyup=キーが離されたときの関数名;
document.onkeypress=キー入力されたときの関数名;

たとえば、次のタグは、onkeydownに応じて、keyDownHanlder関数を呼び出す設定を記述しています。

document.onkeydown=keyDownHandler;

キーボードのイベントでは、それぞれどのキー、あるいは修飾キーが押されたかに対してそれぞれ属性が用意されています。

	event.charCode		押されていたキーのコードの数値(Firefoxのonkeypress用)
	event.keyCode		押されていたキーのコードの数値(onkeydown, onkeyup用)
	event.shiftKey		shiftキーが押されていたらtrue、そうでなければfalse
	event.ctrlKey		controlキーが押されていたらtrue、そうでなければfalse
	event.altKey		alt(Macintoshではoption)キーが押されていたらtrue、そうでなければfalse

次のスクリプトは、実際に押されたキーコードの値を表示領域に表示するものです。


document.onkeydown = keyDownHandler;

function keyDownHandler( event ) {
	var keycode = event.keyCode;

	var canvas = document.getElementById('canvas5');
	var rect = canvas.getBoundingClientRect( );
	var ctx = canvas.getContext('2d');

	ctx.clearRect( 0, 0, rect.right - rect.left, rect.bottom - rect.top );
	ctx.font = "64px 'sans-serif' ";
	ctx.fillText( "Code: " + keycode, 30, 80 );
}

実際に動かしてみましょう。下記のキャンバスに文字入力されたコードが表示されます。

; 表示領域

ボタンとキー入力を使った総合的な例題

画像とアニメーション、そしてキー入力を組み合わせてみます。前に出てきた、鳥のアニメーションですが、これを左右の矢印キーで上下に動かします。上下の矢印キーを使わないのは、長いWebページでは、上下のスクロールに使われるからです。上でキー入力のハンドラを設定していますので、元のハンドラの関数をreservehandlerに保存しておきます。

var gametask = null;
var reservehandler = null;

function controlGame( ) {
	var button = document.getElementById('gamebutton');
	if ( gametask == null ) { 
		gametask = setInterval( updateCanvas, 100 );
		button.value = "Game Stop";
		reservehandler = document.onkeydown;
		document.onkeydown = gameKeyHandler;
	} else {
		clearInterval( gametask );
		gametask = null;
		button.value = "Game Start";
		document.onkeydown = reservehandler;
	}
}

var gamebirdx = 100;
var gamebirdy = 100;

function updateCanvas( ) {
	var canvas = document.getElementById('canvas6');
	var ctx = canvas.getContext('2d');
	ctx.clearRect( gamebirdx, gamebirdy-10, 64, 84 );
	gamebirdx = gamebirdx - 10;
	if ( gamebirdx < -64 ) { gamebirdx = 500; }
	ctx.drawImage( bird, gamebirdx, gamebirdy, 64, 64 );
}

function gameKeyHandler( event ) {
	var keycode = event.keyCode;
	if ( keycode == 37 ) { gamebirdy = gamebirdy - 10; }
	else if ( keycode == 39 ) { gamebirdy = gamebirdy + 10; }
	
	if ( gamebirdy < -64 ) { gamebirdy = 200; }
	if ( gamebirdy > 200 ) { gamebirdy = -64; }
}

実際に動かしてみましょう。

ゲームアニメーション

マウス入力を使った総合的な例題

たとえば、画像が静止していて、それをクリックしたら、何かの選択が行なわれるような場合、 キャンバスを使わなくとも、tableタグとimgタグを利用して、imgタグにonclickのオプションを指定しておき、 関数を呼び出して、処理を行なうというようにした方が、プログラムが簡単になります。 以下のタグとスクリプトの記述は、3つの画像を用意しておいて、画像がクリックされたら、 inputタグのテキストのフィールドに、選択されたメニューが表示されるものになっています。

<table>
<tr><td><img src="caesar.png" width="210" height="117" onclick="selected(0)" /></td>
<td><img src="beaf.png" width="224" height="105"  onclick="selected(1)" /></td>
<td><img src="omulet.png" width="221" height="116"  onclick="selected(2)" /></td></tr>
</table>
<input type="text" id="output" size="30" /><br>
<script type = "text/javascript">
<!--
function selected( n ) {
	var out = document.getElementById( "output" );
	if ( n == 0 ) { out.value = "シーザサラダ"; }
	else if ( n == 1 ) { out.value = "ビーフカレー"; }
	else if ( n == 2 ) { out.value = "オムレツ"; }
}
//-->
</script>

実際に動かせるようにしたものは、以下の通りです。画像をクリックしてみて下さい。

キャンバスの中で一定の領域をクリックしたら、何かの処理が行なわれる場合は、 その領域のx, y座標値の値と比較するようにしなければなりません。 次の例題は、画像を一枚読み込んでおき、その赤い点のエリアが選択されたら、 そのエリアに関する情報が点の上に表示されるようにしています。

// canvasタグ
<canvas id="canvas" width=640 height=300 onmousedown="pointHandler(event)">
</canvas>

// javascriptの部分

var  pointlist=[ [196, 86 ], [326, 210 ], [490, 166 ] ];
var  namelist= [ "beach", "city", "airport" ];
var canvas = document.getElementById( "canvas" );
var ctx = canvas.getContext( "2d" );
var img = new Image(  );
img.src = "island.png";

// bodyタグからonload時にdraw関数を呼び出すようにします
function draw( ) {
	ctx.drawImage( img, 0, 0 );
	ctx.font = "24px 'Times New Roman' ";
}

function pointHandler( event ) {
	var rect = event.target.getBoundingClientRect( );
	var mx = event.clientX - rect.left ;
	var my = event.clientY - rect.top ;
	
	for ( var i in pointlist ) {
		if ( pointlist[ i ][ 0 ] < mx && mx < pointlist[ i ][ 0 ]+30  &&
		     pointlist[ i ][ 1 ] < my && my < pointlist[ i ][ 1 ]+30 ) {
			ctx.fillText( namelist[ i ], pointlist[ i ][ 0 ], pointlist[ i ][ 1 ]-5);
		}
	}
}

実際に動作させて見ましょう。島の地図が見えないときは、再読み込みしてみて下さい。

ちょっとしたアクションゲームのように、アニメーションで画像を動かしていた場合を考えてみます。 その画像がクリックされたら何かの動作が行ないたいような場合は、画像の位置についての情報と マウスのx, y座標値とを比較します。次の例題は、動いている鳥をクリックしたら、 動作が行なわれます。

var ouchbirdtask = setInterval( updateBird, 100 );
var ouchbird = new Image( );
ouchbird.src = "bird.png";

var ouchbirdx = 100;
var ouchbirdy = 100;

function updateBird( ) {
	var canvas = document.getElementById( "canvas" );
	var ctx = canvas.getContext( "2d" );
	ctx.clearRect( ouchbirdx, ouchbirdy-10, 64, 84 );
	ouchbirdx = ouchbirdx - 10;
	if ( ouchbirdx < -64 ) { ouchbirdx = 500; }
	ctx.drawImage( ouchbird, ouchbirdx, ouchbirdy+(ouchbirdx%30), 64, 64 );
}

function birdHandler( event ) {
	var rect = event.target.getBoundingClientRect( );
	var mx = event.clientX - rect.left ;
	var my = event.clientY - rect.top ;

	var canvas = document.getElementById( "canvas" );
	var ctx = canvas.getContext( "2d" );
	ctx.font = "24px 'Times New Roman' ";

	if ( ouchbirdx < mx && mx < ouchbirdx+64  &&
		 ouchbirdy < my && my < ouchbirdy+64 ) {
			ctx.fillText( "Ouch!", ouchbirdx, ouchbirdy+(ouchbirdx%30)+20 );
	}
}