touch, click, pointerの実装 前編 タッチイベントとマウスイベント

このシリーズでは、タッチ/マウスイベント系の実装を考えます。第1回目はタッチイベントとはなにか、また、タッチイベントとマウスイベントの判別について解説します。

発行

著者 高津戸 壮 テクニカルディレクター
touch, click, pointerの実装 シリーズの記事一覧

はじめに

iPhone、Androidが登場したのはもう何年も前ですが、さらにWindows8も登場し、いろいろと状況が変化してきました。Windows8を搭載したPCの多くはタブレットデバイスのように、画面に触れることができるようになっています。

しかしながら、これらのデバイスはマウスを使っても操作できるようにもなっています。今後、どのようにタッチ/マウスのイベントを扱っていけばよいのかは、これからのウェブ開発において、より重要になっていくはずです。

そこでこのシリーズでは、タッチ/マウスイベント系のブラウザへの実装を整理し、それらのブラウザに対して、フロントエンドではどのような実装を施せばよいのかを考えてみたいと思います。

第1回目はタッチイベントとはなにか、また、タッチイベントとマウスイベントの判別について解説します。

今回使用するサンプルは、以下のレポジトリに置いています。

サンプル集

pxgrid/codegrid-touch · GitHub

タッチイベントとは

まず「タッチイベント」について簡単に説明します。これは一般的にiOSやAndroidなどのモバイルデバイスに実装されている、JavaScriptで扱えるイベントのことを指します。タッチイベントを使えば、ユーザーがスクリーンに触れたときの動作を取得することができます。

以下に、W3Cのよるこの仕様の草案(執筆現在では2013年1月24日付け)、Appleによるイベントの詳細があります。

W3Cの仕様は草案ではありますが、iOSやAndroid向けにJavaScriptを書いている人は、このイベントをすでに何度も使っていることでしょう。

このイベントはAppleがiPhoneのために実装したのが初めてのようです。その後、Androidがこれに追従し、同じようにタッチイベントを実装したという経緯があるようです(このへん、ちょっと正確ではないかもしれませんが)。

そのような経緯から、タッチイベントはスマートフォンやタブレットのブラウザ実装において、デファクトスタンダードのような存在になりました。

タッチイベントを使ってみる

タッチイベントで主に使用するのは、次の3種類です。

  • touchstart(タッチが開始された瞬間に発生)
  • touchmove(タッチされた後、指を画面上で動かす度に発生)
  • touchend(タッチされた後、指を画面から話した時に発生)

まずは、これらのイベントを使ったサンプル01-trytouchを見てみます。

サンプル:01-trytouch

codegrid-touch/01-trytouch at gh-pages · pxgrid/codegrid-touch · GitHub

デモでは、タッチするとtouchstart、そのままスワイプするとtouchmove、指を離すとtouchendになる。非タッチデバイスでは、iOSやAndroidシミュレーターで動作を確認できます。

このサンプルをiOSやAndroidなどで見てみると、画面内にある四角をタッチしたとき、発生したイベント名や座標が、画面上に表示されるようになっています。コードを見てみましょう。

HTMLコード

<div id="hitarea">TOUCH ME</div>
<div id="eventname">-</div>
<div id="coords">X:<span id="x"></span> Y:<span id="y"></span></div>

JavaScriptコード

// 要素ら

var el_hitarea = document.getElementById('hitarea');
var el_eventname = document.getElementById('eventname');
var el_x = document.getElementById('x');
var el_y = document.getElementById('y');

// 表示をアップデートする関数群

var updateXY = function(event) {
  el_x.innerHTML = event.changedTouches[0].pageX;
  el_y.innerHTML = event.changedTouches[0].pageY;
};
var updateEventname = function(eventname) {
  el_eventname.innerHTML = eventname;
};

// イベント設定

el_hitarea.addEventListener('touchstart', function(event) {
  updateEventname('touchstart');
  updateXY(event);
  el_hitarea.style.backgroundColor = 'red';
}, false);

el_hitarea.addEventListener('touchmove', function(event) {
  event.preventDefault(); // タッチによる画面スクロールを止める
  updateEventname('touchmove');
  updateXY(event);
}, false);

el_hitarea.addEventListener('touchend', function(event) {
  updateEventname('touchend');
  updateXY(event);
  el_hitarea.style.backgroundColor = 'blue';
}, false);

タッチイベントの設定の仕方は、他のイベントと何ら変わりありません。addEventListenerを使って設定します。ただ、注意しなければならない点がいくつかあります。

まずはtouchmove時のpreventDefaultです。touchmoveは、画面上で指を動かした時に発生するイベントですが、なにもしない場合、これを行うと、画面をスクロールする動作となります。touchstarttouchmoveのイベントハンドラ内で、preventDefaultすることで、これを止めることができます。

このほか指の座標を取るときも注意が必要です。タッチイベントではマウスイベントと異なり、複数の指によるタッチを扱うことが可能になっています。このためタッチされた座標を取る場合は、上記コードのevent.changedTouches[0].pageXのように取得する必要があります。

細かいことをしようとすると、いろいろと手間のかかるタッチイベントではありますが、このようにして、タッチデバイス上でのUI実装を行うことができます。

タッチイベントに関しては、下記の記事に詳しくまとまっていますので、興味がある方はぜひ見てみてください。

タッチイベントの情報

マルチタッチ ウェブブラウザ向けの開発 - HTML5 Rocks

タッチイベントとマウスイベント

先ほどのサンプル(01-trytouch)は非タッチデバイスでは、何も起こりません。それは当然です。マウスをいくら動かしたところで、タッチイベントが起こるはずはありません。

そこで考えだされたのが、次のような実装方法です。次のサンプルは、非タッチデバイスであっても、マウスを使えば同様の動作が再現されます。

サンプル:02-mouseortouch

codegrid-touch/02-mouseortouch at gh-pages · pxgrid/codegrid-touch · GitHub

デモでは、タッチイベントが利用可能かどうか判別し、利用できない場合はマウスイベントを使うような実装をしていることがわかります。

なぜこのようなことができるかというと、まずタッチイベントが利用可能かどうかをチェックし、利用できない場合は、代わりにマウスイベントを使うようにしているためです。コードを見てみましょう。

JavaScriptコード

このサンプル以降、旧IEでも動作させるようにするため、jQueryを使っています。

// 要素ら

var $document = $(document);
var $hitarea = $('#hitarea');
var $eventname = $('#eventname');
var $x = $('#x');
var $y = $('#y');

// タッチイベントが利用可能かの判別

var supportTouch = 'ontouchend' in document;

// イベント名

var EVENTNAME_TOUCHSTART = supportTouch ? 'touchstart' : 'mousedown';
var EVENTNAME_TOUCHMOVE = supportTouch ? 'touchmove' : 'mousemove';
var EVENTNAME_TOUCHEND = supportTouch ? 'touchend' : 'mouseup';

// 表示をアップデートする関数群

var updateXY = function(event) {
  // jQueryのイベントはオリジナルのイベントをラップしたもの。
  // changedTouchesが欲しいので、オリジナルのイベントオブジェクトを取得
  var original = event.originalEvent;
  var x, y;
  if(original.changedTouches) {
    x = original.changedTouches[0].pageX;
    y = original.changedTouches[0].pageY;
  } else {
    x = event.pageX;
    y = event.pageY;
  }
  $x.text(x);
  $y.text(y);
};
var updateEventname = function(eventname) {
  $eventname.text(eventname);
};

// イベント設定

var handleStart = function(event) {
  updateEventname(EVENTNAME_TOUCHSTART);
  updateXY(event);
  $hitarea.css('background-color', 'red');
  bindMoveAndEnd();
};
var handleMove = function(event) {
  event.preventDefault(); // タッチによる画面スクロールを止める
  updateEventname(EVENTNAME_TOUCHMOVE);
  updateXY(event);
};
var handleEnd = function(event) {
  updateEventname(EVENTNAME_TOUCHEND);
  updateXY(event);
  $hitarea.css('background-color', 'blue');
  unbindMoveAndEnd();
};
var bindMoveAndEnd = function() {
  $document.on(EVENTNAME_TOUCHMOVE, handleMove);
  $document.on(EVENTNAME_TOUCHEND, handleEnd);
};
var unbindMoveAndEnd = function() {
  $document.off(EVENTNAME_TOUCHMOVE, handleMove);
  $document.off(EVENTNAME_TOUCHEND, handleEnd);
};

$hitarea.on(EVENTNAME_TOUCHSTART, handleStart);

かなりややこしくなった感じはしますが、1つずつ見ていきます。

タッチイベントのサポート判別

// タッチイベントが利用可能かの判別

var supportTouch = 'ontouchend' in document;

このコードはModernizrで使われていたものを持ってきました。これでタッチイベントが利用可能かを判別できます。そして、これを元に、利用するイベント名を決定します。

// イベント名

var EVENTNAME_TOUCHSTART = supportTouch ? 'touchstart' : 'mousedown';
var EVENTNAME_TOUCHMOVE = supportTouch ? 'touchmove' : 'mousemove';
var EVENTNAME_TOUCHEND = supportTouch ? 'touchend' : 'mouseup';

タッチイベントがサポートされていればタッチ系イベントを、そうでなければマウスイベントをイベント名として変数に入れておきます。そして、先ほどイベントを設定していた部分のイベント名に、この変数を利用します。

$hitarea.on(EVENTNAME_TOUCHSTART, handleStart);

この部分以外でも、1つ目のサンプルではtouchmovetouchendになっていた箇所が、EVENTNAME_TOUCHMOVEEVENTNAME_TOUCHENDになっているのに注意して、コードを読んでみてください。

また、今回のサンプルでは、非タッチデバイスでページを表示した場合、touchmoveの代わりにmousemoveが使われます。mousemoveである場合、mousedownが起こってから、次のmouseupが起こるまでの間(ドラッグしているような状態)だけ、マウスの動きがほしいです。これを実現するため、イベントを付けたり外したりしています。このへんにも注意してください。

このようにタッチイベントが利用可能か否かを判別し、その結果導かれたイベント名を使用することで、iOSやAndroid等のタッチデバイスではタッチイベントを、そうでなければマウスイベントを使用するような実装が行えます。

座標の処理

次に座標の変更を見てみましょう。始めのサンプル(01-trytouch)と比べると、座標を更新する箇所が複雑になっています。

var updateXY = function(event) {
  // jQueryのイベントはオリジナルのイベントをラップしたもの。
  // changedTouchesが欲しいので、オリジナルのイベントオブジェクトを取得
  var original = event.originalEvent;
  var x, y;
  if(original.changedTouches) {
    x = original.changedTouches[0].pageX;
    y = original.changedTouches[0].pageY;
  } else {
    x = event.pageX;
    y = event.pageY;
  }
  $x.text(x);
  $y.text(y);
};

この例ではタッチイベントとマウスイベントの両方を扱うため、changedTouchesが存在すればタッチの座標を、そうでなければマウスの座標を取得するような処理を行っています。

jQueryでDOMのイベントを扱う場合、jQueryは、本来のDOMのイベントをラップした、jQuery独自のイベントオブジェクトを作り、これをさもDOMのイベントオブジェクトであるかのように扱います。このjQuery独自のイベントオブジェクトの中で、ブラウザ間の差異を吸収するような仕組みになっているのです。

しかしながらタッチイベントはそんなに以前からあるものではなく、この例で利用しているchangedTouchesというプロパティを、jQueryのイベントオブジェクトはうまく扱ってくれません。

ですが、大丈夫です。jQueryのイベントオブジェクトにはoriginalEventというプロパティがあり、ここには本来のDOMのイベントオブジェクトが入っています。このオブジェクトのchangedTouchesを参照しているのが、さきほど引用したコードの部分です。

このように、タッチかマウスかを判別し、ひとまとまりのコードで、どちらの動作にも対応するという実装方法が、UI実装に多く用いられていました。jQuery Mobileでも、これと似た実装方法が採用されていました。

ここまでが今までの実装方法

このように、タッチデバイスが登場し、タッチイベントを扱う必要がでてきたわけですが、それとは別に、レスポンシブ・ウェブデザインのように、デスクトップ向けのWebサイトとモバイル向けWebサイトを1つのHTMLでカバーするという方法も、一般的になってきました。

このような状況では、マウス、タッチのどちらかを実装すればよいというわけではなく、両方に対応する必要が出てきました。その解決方法のひとつが、先ほど挙げたような、タッチイベントのサポートを判別して切り分ける方法です。

しかし、この方法は十分ではないことが、Windows8の登場により明らかになりました。Windows8はご存知のように、対応デバイスにインストールされていれば、ひとつのデバイスで画面のタッチも可能ですし、また従来のようにマウスでの操作も可能です。

次回はこのような新しいOSの登場にともなったブラウザのタッチイベントまわりの実装の変化や、そのようなデバイスでの実装を中心に、引き続きタッチイベントについて見ていきます。