今後のタッチ操作対応デバイスで中心的な役割を担うのが Web や Windows 8 Metro スタイルのアプリであることを考えると、Web サイトやアプリでタッチ操作の可能な場所を増やし、その利便性を向上させることがとても重要であると言えます。

今回は、Web 開発者が新しい IE10 ポインター イベント モデルを、iOS タッチ イベント モデルや W3C マウス イベント モデル (拡張仕様) と共に使用して、ポインター入力、タッチ入力、マウス入力を処理する、ブラウザーの種類を問わない共通のコード ハンドラーを作成します。

この記事を書くきっかけについて少し話しておくと、私は運良く Samsung 700T Windows Developer Preview タブレット PC を入手したのです。

この PC では、IE Test Drive サイトでマルチタッチを試すことができるタッチ効果のデモや、鳥を捕まえるデモ Lasso Birds を楽しむことができました。私のように、IE10 以外のさまざまなデバイスやブラウザーで Lasso Birds デモが動作することに気付いた方もいるでしょう。

たとえば、このデモのマルチタッチは、iOS デバイスでも動作します。ここでは、Lasso Birds のパターンをいくつか流用しつつ、それらを古いバージョンのブラウザーもサポートできるように汎用化および拡張しました

実験の結果は、以下に示すとおりです。お使いのブラウザーで動くはずです。コーディング パターンや、学習に役立つ点などをデモの下に説明しています。

マウス、指、またはスタイラスを使って何か描いてみましょう。(SVG で描画)

コード

マウス モデルでの描画の基本アルゴリズムは、単純です。

var drawingStarted = false;

function DoEvent(eventObject) {

if (eventObject.type == "mousedown") {

drawingStarted = true;

startDraw(eventObject.pageX, eventObject.pageY);

}

else if (eventObject.type == "mousemove") {

if (drawingStarted) {

extendDraw(eventObject.pageX, eventObject.pageY);

}

}

else if (eventObject.type == "mouseup") {

drawingStarted = false;

endDraw();

}

}

IE10 のポインター イベントで動作させるために必要な変更は 1 つだけです。複数のポインターが同時に押し下げられる場合を認識し、それぞれを異なる pointerId の値で識別するようにします。

IE10 のポインター モデルでは、状態が変化した各ポインターがそれぞれ個別に MSPointerDown、MSPointerMove、MSPointerUp イベントを発生します。

var drawingStarted = {};

function DoEvent(eventObject) {

eventObject.preventManipulation(); // このコードを省略すると、描画ではなく、パンが実行される

var pointerId = eventObject.pointerId;

if (eventObject.type == "MSPointerDown") {

drawingStarted[pointerId] = true;

startDraw(pointerId, eventObject.pageX, eventObject.pageY);

}

else if (eventObject.type == "MSPointerMove") {

if (drawingStarted[pointerId]) {

extendDraw(pointerId, eventObject.pageX, eventObject.pageY);

}

}

else if (eventObject.type == "MSPointerUp") {

delete drawingStarted[pointerId];

endDraw(pointerId);

}

}

最初のマウス モデルを Apple の iOS タッチ イベント モデルに適用するには、changedTouches のリストを繰り返し処理して touchstart、touchmove、touchend の各イベントを検出する必要があります。これは、iOS モデルでは同時に起こった状態の変化が 1 つのイベントにまとめられるためです。

IE10 のポインター モデルと同様に、一意の識別子によって、タッチしたポイントがそれぞれ識別されます。

var drawingStarted = {};

function DoEvent(eventObject) {

eventObject.preventDefault(); // このコードを省略すると、描画ではなく、パンが実行される

for (var i = 0; i < eventObject.changedTouches.length; ++i) {

var touchPoint = eventObject.changedTouches[i];

var touchPointId = touchPoint.identifier;

if (eventObject.type == "touchstart") {

drawingStarted[touchPointId] = true;

startDraw(touchPointId, touchPoint.pageX, touchPoint.pageY);

}

else if (eventObject.type == "touchmove") {

if (drawingStarted[touchPointId]) {

extendDraw(touchPointId, touchPoint.pageX, touchPoint.pageY);

}

}

else if (eventObject.type == "touchend") {

delete drawingStarted[touchPointId];

endDraw(touchPointId);

}

}

}

これらの 3 つのアルゴリズムを 1 つにまとめるには、イベント名と一意のポインター識別子の属性名に注意する必要があります。また、マウス モデルには識別子がないことにも注意が必要です。

1 つにまとめた下記のモデルには、"移動" した位置が本当に変化しているかどうかのチェックも追加しています。これは、IE10 のポインター モデルでは タッチ ポイントが移動なしで押し下げられた場合に、x、y が同じ位置で MSPointerMove イベントが発生するためです。

このような余分な移動をフィルターで排除することにより、extendDraw() を呼び出しても何も実行されないという状況を回避しました。このチェックの実装では、開始点または移動点から最新の x と y の位置を lastXY オブジェクトに保存して、特定の id に対する lastXY エントリが存在するかどうかをチェックしています。

lastXY は、前の 2 つの例で使用している drawingStarted オブジェクトを置き換えるオブジェクトです。

var lastXY = { };

function DoEvent(eventObject) {

// 描画できるように、パンと拡大縮小を停止

if (eventObject.preventManipulation)

eventObject.preventManipulation();

else

eventObject.preventDefault();

 

// changedTouches の配列がある場合は、その配列を使用。ない場合は、eventObject を使って同様の配列を作成

var touchPoints = (typeof eventObject.changedTouches != 'undefined') ? eventObject.changedTouches : [eventObject];

for (var i = 0; i < touchPoints.length; ++i) {

var touchPoint = touchPoints[i];

// 一意の touchPoint ID がある場合は、その ID を使用。ない場合は、既定値として 1 を使用

var touchPointId = (typeof touchPoint.identifier != 'undefined') ? touchPoint.identifier : (typeof touchPoint.pointerId != 'undefined') ? touchPoint.pointerId : 1;

 

if (eventObject.type.match(/(down|start)$/i)) {

// mousedown、MSPointerDown、touchstart を処理

lastXY[touchPointId] = { x: touchPoint.pageX, y: touchPoint.pageY };

startDraw(touchPointId, touchPoint.pageX, touchPoint.pageY);

}

else if (eventObject.type.match(/move$/i)) {

// mousemove、MSPointerMove、touchmove を処理

if (lastXY[touchPointId] && !(lastXY[touchPointId].x == touchPoint.pageX && lastXY[touchPointId].y == touchPoint.pageY)) {

lastXY[touchPointId] = { x: touchPoint.pageX, y: touchPoint.pageY };

extendDraw(touchPointId, touchPoint.pageX, touchPoint.pageY);

}

}

else if (eventObject.type.match(/(up|end)$/i)) {

// mouseup、MSPointerUp、touchend を処理

delete lastXY[touchPointId];

endDraw(touchPointId);

}

}

}

 

上の例では、イベントを受け取るための登録に関する問題や、描画ターゲットへの適用を確認する問題は、あえて無視しています。これを実際にすべてのブラウザー (IE9 以前の Internet Explorer を含む) で動かすには、もう少し作業が必要になります。興味のある方は、マルチブラウザー対応のマルチタッチ描画クラスの完成版を、こちらから入手してください。

マウス操作とタッチ操作の両方のコードを作成することで、Web 開発者は、デスクトップ、タブレット、携帯電話のどのブラウザーでもサイトを確実に動作させることができるようになります。

—Ted Johnson (グラフィックス リード プログラム マネージャー、Internet Explorer)