javaScript HTML5 canvasイベント整理2 (マウスイベント編)

イベント作成
キーボード絡みはこちら。
キーボードイベント作成

クリック、ダブルクリック、マウスが動いた等マウスがらみのイベントを
処理したければ、全てのイベントを登録する必要があります。
登録の仕方は前回書いたものをそのまま利用できるので以下のようになります。


( function () {
	var canvas = document.getElementById("mesh"),
		event_history = [],
		MAX_HISTORY = 5,
		current = 0,
		log = function(e){
			var log = document.getElementById("event_log");
			event_history[current] = e.type;
			current += 1;
			current %= MAX_HISTORY;
			log.innerHTML = event_history.join("");
		};

	//マウスがらみを全て処理したいなら全部書く。
	event.add(canvas, "click", log);
	event.add(canvas, "dblclick", log);
	event.add(canvas, "mouseup", log);
	event.add(canvas, "mousedown", log);
	event.add(canvas, "mousemove", log);
	event.add(canvas, "mouseout",  log);
	event.add(canvas, "mousewheel", log);
	event.add(canvas, "DOMMouseScroll", log);
}());

ただ、これだとイベントが発生したことは分かっても、どこの座標をクリックしたか、
どのボタンが押されたか、どの程度ホイールされたかは分かりません。
そこで、それらを解析する処理を追加する必要があります。

1.要素上の左上を原点と考え場合のマウス位置を取得したい。

色々ややこしいことがあるので結論だけ示します。
以下の4つの関数を定義しますが、使うのはget_mouse_positionのみ。
get_mouse_positionにイベントオブジェクトを渡してあげれば、マウス位置を取得することが出来ます。

window_info = function(){
	var wininfo = {};
	if(window.innerWidth){
		wininfo.horizontal_scroll = window.pageXOffset;
		wininfo.vertical_scroll = window.pageYOffset;
	}else{
		if(document.documentElement && document.documentElement.clientWidth){
			wininfo.horizontal_scroll = document.documentElement.scrollLeft;
			wininfo.vertical_scroll = document.documentElement.scrollTop;
		}else{
			if(document.body.clientWidth){
				wininfo.horizontal_scroll = document.body.scrollLeft;
				wininfo.vertical_scroll = document.body.scrollTop;
			}
		}
	}
	return wininfo;
},
get_mouseX = function(el){
	var x = 0,
		e;
	for(e = el; e; e = e.offsetParent){
		x += e.offsetLeft;
	}
	for(e = el.parentNode; e && e !== document.body; e = e.parentNode){
		if(e.scrollLeft){
			x -= e.scrollLeft;
		}
	}
	return x;
},
get_mouseY = function(el){
	var y = 0,
		e;
	for(e = el; e; e = e.offsetParent){
		y += e.offsetTop;
	}
	for(e = el.parentNode; e && e !== document.body; e = e.parentNode){
		if(e.scrollTop){
			y -= e.scrollTop;
		}
	}
	return y;
},
get_mouse_position = function(e){
	var el = e.target,
		wininfo = window_info();
	return {
		x: e.clientX + wininfo.horizontal_scroll - get_mouseX(el),
		y: e.clientY + wininfo.vertical_scroll - get_mouseY(el)
	};
};

ここまでのサンプルはこちら。
さんぷる3

2.要素の中央を原点とした場合のマウス位置を取得したい。

これは、何のことはない要素の中央が原点になるように移動してあげるだけです。
中央なので必要となる値は、canvas要素の横幅と高さのそれぞれ半分のサイズです。
一応座標系を決めておきますが、横は中央から右がプラスで左がマイナス。
縦は中央から上がプラスで下がマイナスとしました。
一応canvasを前提としているので、要素の幅と高さは、
canvas.width、canvas.heightで取得できます。
(上記canvasは、一番上の例の要素の事)

//element_half_width: 要素の半分の横幅
//element_half_height:要素の半分の高さ
convert_mouse = function(position){
	var result = {};
    result.x = (position.x - element_half_width);
    result.y = (element_half_height - position.y);
    return result;
};

ここまでのサンプルはこちら。
さんぷる4

3.マウスのどのボタンが押されたかを判別したい。

eventオブジェクトのbutton属性で判別します。
詳しくはこちらを参照してください。

https://developer.mozilla.org/ja/DOM/event.button

4.マウスホイールを処理したい。
event.detailまたはevent.wheelDeltaで判断します。

詳しくはこちらを参照してください。
http://www.adomas.org/javascript-mouse-wheel/
取得できる値も異なりますので、ちょっとした調整をする必要があります。
ぶっちゃけこれだけでもいいのかな~って思います。
下記のようにすることで、wheelの変化量を1刻みで取得できます。

var wheel_delta = e.detail ? e.detail / -3 : e.wheelDelta / 120;

ここまでのサンプルはこちら。
さんぷる5

ここまでを纏めてみようと思いますが、前回作ったevent.jsも今回作るマウス絡みも
それぞれ別々に作っても仕方が無いので、1つに纏めてみようと思います。
今回は[Input]という名前空間を1つ作成しそこに、前回作成したeventと今回作成する
mouseを両方突っ込んでみます。
名前空間を作成するには、javaScriptパターンより拝借したnamespaceパターンを採用します。
詳しくはこちらを。

http://redchant.exblog.jp/15993899/

var Input = Input || {};
Input.namespace = function(ns_string){
	var parts = ns_string.split('.'),
		parent = Input,
		i;
	if(parts[0] === "Input"){
		parts = parts.slice(1);
	}
	for(i = 0; i < parts.length; i += 1){
		if(typeof parent[parts[i]] === "undefined"){
			parent[parts[i]] = {};
		}
		parent = parent[parts[i]];
	}
	return parent;
};

まぁ、規模的にファイルを分けるのもめんどくさいのでInput.jsの中に全て作っちゃいましょう。
まずは、前回作ったevent.jsをInput.jsに移動します。
また、先程まで解説したマウス絡みの処理もInput.Mouseの中に纏めてしまいましょう。

/*****************************************************************************
 * Input.Mouse
 * [マウスイベント処理]
 * element : canvas要素
 *
 * [機能]
 * update : マウスの状態を更新する処理(ループ処理で毎回呼ぶ必要がある)
 *
 * [update後利用できる機能]
 * get_click_position    : クリックが発生した場所がを返す。(左上基準座標)
 *                         クリックしていない場合はfalseを返す。
 * get_dbclick_position  : ダブルクリックが発生した場所が入る(左上基準座標)
 *                         ダブルクリックしていない場合はfalseを返す。
 * get_position          : マウスの場所が入る(左上基準座標)
 * get_wheel_delta       : ホイールの変化量(1ずつ)が入る。
 * get_button_states     : マウスの各ボタンが押されているかを返す。
 *
 *
 *[直接参照するプロパティ]
 * absoluteX             : マウスの場所(X座標)が入る(要素中央を基準座標)
 * absoluteY             : マウスの場所(Y座標)が入る(要素中央を基準座標)
 * relativeX             : 前回のマウス位置からの変化量(X座標)(要素中央を基準座標)
 * relativeY             : 前回のマウス位置からの変化量(Y座標)(要素中央を基準座標)
 ****************************************************************************/
Input.Mouse = function(element){
	//依存関係
	var Event = Input.event,
		Mouse = Input.Mouse.prototype,
		i,
		that;
	if(!(this instanceof Input.Mouse)){
		return new Input.Mouse(element);
	}
	that = this;
	this.element = element;
	this.element_width = element.width;
	this.element_height = element.height;
	this.element_half_width = element.width / 2;
	this.element_half_height = element.height / 2;

	this.position = null;
	this.absoluteX = null;
	this.absoluteY = null;
	this.relativeX = 0;
	this.relativeY = 0;

	this.curr_wheel_delta = 0;
	this.prev_wheel_delta = 0;
	this.wheel_delta = 0;

	this.index = 0;
	this.movementX = [0, 0];
	this.movementY = [0, 0];

	this.button_states = [
							[0, 0, 0],	// current
							[0, 0, 0],	// prev
							[0, 0, 0]	// temp
						];
	this.curr_button_states = this.button_states[0];
	this.prev_button_states = this.button_states[1];
	this.temp_button_states = this.button_states[2];

	this.click_states = [
							[null, null],	// current
							[null, null]	// temp
						];
	this.curr_click = this.click_states[0];
	this.temp_click = this.click_states[1];

	this.dbclick_states = [
							[null, null],	// current
							[null, null]	// temp
						];
	this.curr_dbclick = this.dbclick_states[0];
	this.temp_dbclick = this.dbclick_states[1];

	//add event
	Event.add(this.element, "click", function(e){Mouse.message.call(that, e)});
	Event.add(this.element, "dblclick", function(e){Mouse.message.call(that, e)});
	Event.add(this.element, "mouseup", function(e){Mouse.message.call(that, e)});
	Event.add(this.element, "mousedown", function(e){Mouse.message.call(that, e)});
	Event.add(this.element, "mousemove", function(e){Mouse.message.call(that, e)});
	Event.add(this.element, "mouseout",  function(e){Mouse.message.call(that, e)});
	Event.add(this.element, "mousewheel", function(e){Mouse.message.call(that, e)});
	Event.add(this.element, "DOMMouseScroll", function(e){Mouse.message.call(that, e)});
};
Input.Mouse.prototype =(function(){
	var Event = Input.event,
		BUTTON_LEFT   = 0,
		BUTTON_MIDDLE = 1,
		BUTTON_RIGHT  = 2,

		window_info = function(){
			var wininfo = {};
			if(window.innerWidth){
				wininfo.horizontal_scroll = window.pageXOffset;
				wininfo.vertical_scroll = window.pageYOffset;
			}else{
				if(document.documentElement && document.documentElement.clientWidth){
					wininfo.horizontal_scroll = document.documentElement.scrollLeft;
					wininfo.vertical_scroll = document.documentElement.scrollTop;
				}else{
					if(document.body.clientWidth){
						wininfo.horizontal_scroll = document.body.scrollLeft;
						wininfo.vertical_scroll = document.body.scrollTop;
					}
				}
			}
			return wininfo;
		},
		getX = function(el){
			var x = 0,
				e;
			for(e = el; e; e = e.offsetParent){
				x += e.offsetLeft;
			}
			for(e = el.parentNode; e && e !== document.body; e = e.parentNode){
				if(e.scrollLeft){
					x -= e.scrollLeft;
				}
			}
			return x;
		},
		getY = function(el){
			var y = 0,
				e;
			for(e = el; e; e = e.offsetParent){
				y += e.offsetTop;
			}
			for(e = el.parentNode; e && e !== document.body; e = e.parentNode){
				if(e.scrollTop){
					y -= e.scrollTop;
				}
			}
			return y;
		},
		get_position = function(e){
			var el = e.srcElement || e.target,
				wininfo = window_info();

			return {
				x: e.clientX + wininfo.horizontal_scroll - getX(el),
				y: e.clientY + wininfo.vertical_scroll - getY(el)
			};
		};

	return {
		BUTTON_LEFT: BUTTON_LEFT,
		BUTTON_RIGHT: BUTTON_RIGHT,
		BUTTON_MIDDLE: BUTTON_MIDDLE,

		convert_position: function(position){
			return {
				x: (position.x - this.element_half_width),
				y: (this.element_half_height - position.y)
			}
		},

		get_click_position: function(){
			if(this.curr_click[0] === null){
				return false;
			}
			return {
				x: this.curr_click[0],
				y: this.curr_click[1]
			};
		},
		get_dbclick_position: function(){
			if(this.curr_dbclick[0] === null){
				return false;
			}
			return {
				x: this.curr_dbclick[0],
				y: this.curr_dbclick[1]
			};
		},
		get_position: function(){
			return this.position;
		},
		get_wheel_delta: function(){
			return  this.wheel_delta;
		},
		get_button_states: function(button){
			button = button || BUTTON_LEFT;
			return this.curr_button_states[button];
		},
		message: function(e){
			var position = get_position(e),
				button = e.button,
				dx = 0, dy = 0,result
				i;
			this.position = position;
			result = this.convert_position(position);
			if(this.absoluteX !== null){
				dx = result.x - this.absoluteX;
				dy = result.y - this.absoluteY;
			}
			this.relativeX = dx;
			this.relativeY = dy;
			switch(e.type){
				case "click":
					this.temp_click[0] =  position.x;
					this.temp_click[1] =  position.y;
					break;
				case "dblclick":
					this.temp_dbclick[0] =  position.x;
					this.temp_dbclick[1] =  position.y;
					break;
				case "mouseup":
					this.temp_button_states[button] = false;
					break;
				case "mousedown":
					this.temp_button_states[button] = true;
					break;
				case "mousemove":
					this.absoluteX = result.x;
					this.absoluteY = result.y;
					break;
				case "mousewheel":
				case "DOMMouseScroll":
					this.curr_wheel_delta += e.detail ? e.detail / - 3 : e.wheelDelta / 120;
					break;
				case "mouseout":
					this.absoluteX = null;
					this.absoluteY = null;
					this.relativeX = 0;
					this.relativeY = 0;

					this.curr_button_states[0] = false;
					this.curr_button_states[1] = false;
					this.curr_button_states[2] = false;

					this.prev_button_states[0] = false;
					this.prev_button_states[1] = false;
					this.prev_button_states[2] = false;

					this.temp_button_states[0] = false;
					this.temp_button_states[1] = false;
					this.temp_button_states[2] = false;
					break;
				default:
					break;
			}
			Event.stop_event(e);
		},
		update: function(){
			this.curr_click[0] = this.temp_click[0];
			this.curr_click[1] = this.temp_click[1];
			this.curr_dbclick[0] = this.temp_dbclick[0];
			this.curr_dbclick[1] = this.temp_dbclick[1];
			this.temp_click[0] = null;
			this.temp_click[1] = null;
			this.temp_dbclick[0] = null;
			this.temp_dbclick[1] = null;

			this.prev_button_states[0] = this.curr_button_states[0];
			this.prev_button_states[1] = this.curr_button_states[1];
			this.prev_button_states[2] = this.curr_button_states[2];

			this.prev_button_states[0] = this.curr_button_states[0];
			this.prev_button_states[1] = this.curr_button_states[1];
			this.prev_button_states[2] = this.curr_button_states[2];

			this.curr_button_states[0] =(this.temp_button_states[0]) ? true : false;
			this.curr_button_states[1] =(this.temp_button_states[1]) ? true : false;
			this.curr_button_states[2] =(this.temp_button_states[2]) ? true : false;

		    this.wheel_delta = (this.curr_wheel_delta - this.prev_wheel_delta);
		    this.prev_wheel_delta = this.curr_wheel_delta;
		},
		print:function(){
			var result = "";
			result += "positionX" + this.position.x + "";
			result += "positionY" + this.position.y + "";

			result += "relativeX" + this.relativeX + "";
			result += "relativeY" + this.relativeY + "";

			result += "absoluteX" + this.absoluteX + "";
			result += "absoluteY" + this.absoluteY + "";

			result += "wheel_delta" + this.wheel_delta + "";

			result += "button_states[LEFT]" + this.curr_button_states[BUTTON_LEFT] + "";
			result += "button_states[BUTTON_MIDDLE]" + this.curr_button_states[BUTTON_MIDDLE] + "";
			result += "button_states[BUTTON_RIGHT]" + this.curr_button_states[BUTTON_RIGHT] + "";

			result += "clickX" + this.curr_click[0] + "";
			result += "clickY" + this.curr_click[1] + "";

			result += "dbclickX" + this.curr_dbclick[0] + "";
			result += "dbclickY" + this.curr_dbclick[1] + "";
			return result;
		}
	};
}());

これで、マウス周りもすっきりしたので、canvasを使ったお絵かきでも作ってみます。

var canvas = document.getElementById("mesh"),
				ctx,
				prev = null,
				mouse = new Input.Mouse(canvas);// これだけになる。

			//canvas絡みのお手続き
			if(canvas.getContext){
				ctx = canvas.getContext('2d');
			}else{
				throw{
					name:"Error",
					message:"canvas not support!!!",
				};
			}
			//ここから繰り返し
			setInterval(function(){
				mouse.update();//マウス情報の更新
				var position = mouse.get_position();

				//左ボタンが押された常態か?
				if(mouse.get_button_states()){
					if(!prev){
						prev = {x: position.x, y: position.y};
					}
					ctx.beginPath();
					ctx.moveTo(prev.x, prev.y);
					ctx.lineTo(position.x, position.y);
					ctx.closePath();
					ctx.strokeStyle = "#f00";
					ctx.stroke();
					prev = {x: position.x, y: position.y};
				}else{
					//押されてないなら線を書くのを止める。
					prev = null;
				}

			},1000/60);

実はこれだけで、マウスドラッグでお絵かきができる。
ここまでのサンプルはこちら。
さんぷる7

ちょっぴり違う筆の感じ。
さんぷる8
さんぷる9

ここまでで作成したInput.jsはこちら。
Input.js

This entry was posted in HTML5 Canvas, javaScript. Bookmark the permalink.

コメントを残す

メールアドレスが公開されることはありません。

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>