マウス・タッチ・ペンのタッチ操作イベント

Updated / Published

Windows 8のようなOSの登場により、Web制作においてもデスクトップであれマウスとタッチの両方を同時にサポートする必要が出てきました。加えて、Surfaceを筆頭にペン圧やペンの傾きなども検知できる入力メカニズムとしてペン入力も今後重要になってくるということで、従来のマウスイベント(mouse*)やタッチイベント(touch*)を包括し、なおかつペン入力にも対応するポインタイベント(pointer*)がMicrosfot社より提唱されました。IE10よりこれを先攻実装、2012年11月にW3CにてPointer Eventsワーキンググループが発足、翌2013年5月に勧告候補となり、IE11よりこの勧告候補仕様をサポートしており、今後他の主要ブラウザにおいてもサポートが見込まれます。

本エントリーでは現状のタッチ操作周りのイベントについて、フリック操作のスクリプトを実装する形を想定してそれぞれのイベントをクロスブラウザでフォールバックしながら用いる方法を解説していきます。解説のコードには、フォールバックを含めてわかりやすく簡素化するためにjQueryを用いるものとします。

タッチ操作のイベント比較

新たにペン入力を含めた総合的なポインタイベントは従来のデスクトップのマウスイベント、タブレットやスマートフォンのタッチイベントと同じように使えるので、基本的にはイベント名を変えるだけで対応はできます。それぞれに相当するイベントは次のようにまとめることができます。

マウスタッチ総合(ポインタ)
mousedowntouchstartpointerdown
mouseentertouchenterpointerenter
mouseleavetouchleavepointerleave
mousemovetouchmovepointermove
mouseout-pointerout
mouseover-pointerover
mouseuptouchendpointerup
-touchcancel pointercancel

UAが各イベントに対応しているかどうか

var _ua = (function(){
 return {
  Touch:typeof document.ontouchstart != "undefined",
  Pointer:window.navigator.pointerEnabled,
  MSPoniter:window.navigator.msPointerEnabled
 }
})();

UAがタッチイベントをサポートしているかどうかはdocument.ontouchstartundefinedを返さないことを条件にし、ポインタイベントをサポートしているかどうかは、navigator.pointerEnabledwindowオブジェクトは省略可)を用います。なおIE10は先攻実装のため、ベンダー識別子を用いる必要があります。

対応するイベント名を割り当てる

先の比較表に応じて、必要なイベントをわかりやすい変数名に格納して使うようにすると良いでしょう。フリック操作であれば、pointerdown, pointermove, pointerupに応じた他のマウスとタッチのイベントを割り当てます。

var _start = _ua.Pointer ? 'pointerdown' : _ua.MSPointer ? 'MSPointerDown' : _ua.Touch ? 'touchstart' : 'mousedown';
var _move  = _ua.Pointer ? 'pointermove' : _ua.MSPointer ? 'MSPointerMove' : _ua.Touch ? 'touchmove' : 'mousemove';
var _end   = _ua.Pointer ? 'pointerup' : _ua.MSPointer ? 'MSPointerUp' : _ua.Touch ? 'touchend' : 'mouseup';
  • 変数_startにUAの対応状況に応じて順にpointerdown, MSPointerDown, touchstart, mousedownのいずれかのイベント名が格納されるようにします。
  • 変数_moveにUAの対応状況に応じて順にpointermove, MSPointerMove, touchmove, mousemoveのいずれかのイベント名が格納されるようにします。
  • 変数_endにUAの対応状況に応じて順にpointerup, MSPointerUp, touchend, mouseupのいずれかのイベント名が格納されるようにします。

デフォルトの挙動を無効化しておく

#target {
	-ms-touch-action : none ; /* for *IE10 */
	touch-action : none ;
}

タッチデバイスは、タッチしながら画面をスクロールしたり、二本の指でピンチ操作やストレッチ操作を行うとページ全体を縮小したり拡大したりしますが、タッチ操作を扱うコンテンツにおいては、このデフォルトの挙動が邪魔をします。そのため、これらの既定のタッチ処理をCSSで無効にする必要があります。この設定をしておかないと、ポインタイベントでタッチ操作を意図通りに扱うことができなくなります(ポインタイベントが正常に発生しない)ので、注意してください。

touch-actionプロパティは、IE11よりサポートしています。IE10では、ベンダー識別子つきの-ms-touch-actionプロパティを用います。

各イベントを設定する

var _target = $('#target');
//変数_startに格納されたイベント
_target.on(_start, function(e){
	var point  = e.originalEvent.changedTouches ? e.originalEvent.changedTouches[0] : e;
	var startX = point.pageX;
	var startY = point.pageY;
	var setX   = 0;
	var setY   = 0;
	//変数_moveに格納されたイベント
	_target.on(_move, function(e){
		point	= e.originalEvent.changedTouches ? e.originalEvent.changedTouches[0] : e;
		setX = point.pageX - startX;
		setY = point.pageY - startY;
		if(Math.abs(setX) > 5){
			//for Android
			e.preventDefault();
			e.stopPropagation();
		}else if(Math.abs(setY) > 5){
			//縦スクロール時
			return;
		}
		/*
		  フリック中の処理
		*/
	});
	//変数_endに格納されたイベント
	//このイベントは対象要素ではなく、documentを対象にする
	$(document).on(_end, function(e){
		point	= e.originalEvent.changedTouches ? e.originalEvent.changedTouches[0] : e;
		setX = point.pageX - startX;
		setY = point.pageY - startY;
		if(setX < -5){
			/*
			  左にフリックされた場合の処理
			*/
		}else if(setX > 5){
			/*
			  右にフリックされた場合の処理
			*/
		}
		//最後にイベントを削除
		_target.off(_move);
		$(document).off(_end);
	});
});

point = e.originalEvent.changedTouches ? e.originalEvent.changedTouches[0] : e;は、タッチイベントから座標を取得するには、changedTouchesプロパティから取得する必要があり、またjQueryを用いているのであわせてoriginalEventオブジェクトにアクセスする必要があります。そこで、e.originalEvent.changedTouchesが存在するかどうかで座標を取得するための変数pointにタッチイベント用の座標とマウスイベント&ポインタイベント用の座標とをわけて格納できるようにします。

_moveに格納されたイベントの中でAndroidではtouchmove中にe.preventDefault();を設定しなければ、touchendが正常に発生しません。ただし、縦スクロールまでを無効化してしまうため、X軸方向にフリックされているMath.abs(setX) > 5を条件にe.preventDefault();を設定するようにします。

以上ここまで紹介してきた点を抑えておけば、クロスブラウザでのフォールバックをしながら、マウスイベント、タッチイベント、ポインタイベントをそれぞれ上手く使い分けることができるでしょう。

以降はポインタイベントのみに該当する内容となります。

マウス・タッチ・ペンの検出

if(_ua.Pointer || _ua.MSPointer){
	if( e.pointerType == "mouse"||4 ) {
		/*マウスの場合*/
	}else if( e.pointerType == "touch"||2 ) {
		/*タッチの場合*/
	}else if( e.pointerType == "pen"||3 ) {
		/*ペンの場合*/
	}
}

pointerTypeプロパティを用いて、ポインタイベントのイベントソースがマウスかタッチかペンかを検出できます。勧告候補仕様では、mouse, touch, penの文字列を返しますが、先攻実装のIE10ではマウスの場合は4, タッチの場合は2, ペンの場合は3の整数値を返します。

ペンやタッチの場合にマルチタッチ機能に対応しているかどうか

if( window.navigator.maxTouchPoints && window.navigator.maxTouchPoints > 1) {
 ...
}

navigator.maxTouchPointsプロパティ(windowオブジェクトは省略可)でペンやタッチの場合の最大タッチ数を取得できます。最大タッチ数が2以上あることを条件にマルチタッチ機能に対応しているハードウェアかどうかを判別できます。

なお、ポインタイベントにはpointercancelがありますが、このイベントが発生するのは稀で、画面上で同時タッチポイントが2つまでサポートされているハードウェアであれば、3つ目のポイントが画面に追加されたときに、同時に3つのポイントは追跡できないために、ポイントの1つが取り消されて、pointercancelのイベントが発生することになります。

参考リソース

navigatorオブジェクトのポインタに関するプロパティ

pointerEnabled
ユーザエージェントがポインタイベントを実装していればtrueを、そうでなければfalseを返します。
maxTouchPoints
ハードウェアがサポートしているペンやタッチの場合の最大タッチ数を取得できます。

ポインタイベント

pointerdown
指がタッチパネルに触れた、または、マウスのボタンを押した、または、ペンがデジタイザーに触れたときに発生します。
pointerenter
ポインタが対象要素または子孫要素の境界に入ったときに発生します。pointeroverと似ていますがこのイベントは、イベントバブリングを補足しませんので、対象要素の上に別の要素が重なる場合は1度しか発生しない違いがあります。
pointerleave
ポインタが対象要素または子孫要素の境界を越えて中から外に出たときに発生します。pointeroutと似ていますがこのイベントは、イベントバブリングを補足しませんので、対象要素の上に別の要素が重なる場合は1度しか発生しない違いがあります。
pointermove
指をタッチパネルに触れたまま動かしたとき、または、マウスのボタンを押さない状態でカーソルを動かしたとき、または、ペンをデジタイザーに触れた状態で動かしたときに発生します。
pointerout
指をタッチパネルに触れたままで動かした状態で、または、マウスのボタンを押しているかどうかにかかわらずマウスのカーソルを動かしている状態で、または、ペンがデジタイザーに触れているかどうかにかかわらずペンを動かしている状態で、それらのポインタが要素の境界を越えて中から外に出たときに発生します。
pointerover
指をタッチパネルに触れたままで動かした状態で、または、マウスのボタンを押しているかどうかにかかわらずマウスのカーソルを動かしている状態で、または、ペンがデジタイザーに触れているかどうかにかかわらずペンを動かしている状態で、それらのポインタが要素の境界を超えて外から中に入ったときに発生します。
pointerup
指をタッチパネルから離した、または、マウスのボタンを離した、または、ペンをデジタイザーから離したときに発生します。
pointercancel
システムがポインタを破棄したときに発生します。このイベントが発生することは希ですが、具体的には、タッチ入力またはペン入力を使っているとき、画面上のポインタが取り消される場合があります。たとえば、画面上で同時タッチポイントが2つまでサポートされているときに3つ目のポイントを画面に追加した場合、ハードウェアは3つのポイントを追跡できないので、ポイントの1つが取り消されます。いくつまでのタッチポイントをサポートできるかはwindow.navigator.maxTouchPointsプロパティを使用。

ポインタイベントオブジェクトのプロパティ

isPrimary
プライマリーポインタならtrueを、そうでなければfalseを返します。プライマリーポインタとは、最初の接触を意味します。たとえば、二本の指でタッチした場合、必ず一方が最初に接触するはずです。この最初の接触ではtrueを、二本目の接触ではfalseを返します。マウスの場合は常にtrueを返します。
pointerId
タッチ、マウス、ペンの接触の一意的な識別子を数値で返します。
pointerType
イベントのソースがタッチ、ペン、マウスのどれに相当するのかを文字列で返します。touchはタッチ, penはペン, mouseはマウスを表します。なお、IE10は整数値を返し2はタッチ、3はペン、4はマウスを表します。
pressure
ペン圧を0~1の範囲の数値で返します。指でタッチした場合や、マウスのボタン押すなどのアクティブな接触の場合は値 0.5 を返し、それ以外の場合、筆圧をサポートしないハードウェアでは 0 を返します。IE10ではペン以外は常に0を返します。
tiltX
ペンの左右の傾き角度を-90~90の範囲の数値で返します。右方向に傾けば正の値になり、左方向に方向けば負の値になります。タッチ、マウスの場合は常に0となります。
tiltY
ペンの前後の傾き角度を-90~90の範囲の数値で返します。手前に傾けば正の値になり、前方に傾けば負の値になります。タッチ、マウスの場合は常に0となります。