マルチタッチ操作

ポインタイベントは DOM 入力イベントを拡張して、マウスだけでなくペン/スタイラスやタッチ画面などのさまざまなポインティング入力デバイスをサポートします。 ポインタは、特定の画面座標セットをターゲットにできるハードウェアにとらわれないデバイスです。 ポインタに単一のイベントモデルを使用すると、ウェブサイト、ウェブアプリの作成が簡単になり、ユーザーのハードウェアに関係なく優れたユーザーエクスペリエンスを提供できます。

ポインタイベントはマウスイベントと多くの類似点がありますが、タッチ画面上の複数の指など、複数同時のポインタをサポートしています。 この追加機能は、より豊富なユーザー操作モデルを提供するために使用できますが、マルチタッチ操作(multi-touch interaction)の処理がさらに複雑になります。 このドキュメントは、異なるマルチタッチ操作を持つポインタイベントを使用して、コード例を介して説明します。

このアプリのライブ版は Github で利用できます。 ソースコードは Github で入手できます。 プルリクエストやバグレポートは大歓迎です。

この例では、さまざまなマルチタッチ操作にポインタイベントのさまざまなイベントタイプ(pointerdownpointermovepointeruppointercancel など)を使用する方法を示します。

タッチターゲットの定義

アプリは <div> を使用して3つの異なるタッチターゲット領域を定義します。

<style>
  div {
    margin: 0em;
    padding: 2em;
  }
  #target1 {
    background: white;
    border: 1px solid black;
  }
  #target2 {
    background: white;
    border: 1px solid black;
  }
  #target3 {
    background: white;
    border: 1px solid black;
  }
</style>

グローバルな状態

マルチタッチ操作をサポートするには、さまざまなイベントフェーズの間にポインタのイベント状態を維持することが必要です。 このアプリは、イベント状態をキャッシュするために、ターゲット要素ごとに1つのキャッシュで、3つの配列を使用します。

// Log events flag
// イベントログフラグ
var logEvents = false;

// Event caches, one per touch target
// タッチターゲットごとに1つのイベントキャッシュ
var evCache1 = new Array();
var evCache2 = new Array();
var evCache3 = new Array();

イベントハンドラの登録

イベントハンドラは pointerdownpointermovepointerup のポインタイベントに登録します。 pointerup ハンドラは pointercancelpointeroutpointerleave のイベントにも使用します。 これら4つのイベントは、このアプリでは同じ意味を持っているからです。

function set_handlers(name) {
 // Install event handlers for the given element
 // 与えられた要素にイベントハンドラをインストールする
 var el=document.getElementById(name);
 el.onpointerdown = pointerdown_handler;
 el.onpointermove = pointermove_handler;

 // Use same handler for pointer{up,cancel,out,leave} events since
 // the semantics for these events - in this app - are the same.
 // pointer{up,cancel,out,leave} イベントの意味は - このアプリでは -
 // 同じであるため、これらのイベントに同じハンドラを使用する。
 el.onpointerup = pointerup_handler;
 el.onpointercancel = pointerup_handler;
 el.onpointerout = pointerup_handler;
 el.onpointerleave = pointerup_handler;
}

function init() {
 set_handlers("target1");
 set_handlers("target2");
 set_handlers("target3");
}

ポインタダウン

pointerdown イベントは、ポインタ(マウス、ペン/スタイラス、タッチ画面上のタッチポイント)が接触面に接触したときに発生します。 このダウンイベントがマルチタッチ操作の一部である場合、イベントの状態をキャッシュしなければなりません。

このアプリでは、要素の上にポインタを置いてダウンすると、その要素が持つアクティブなタッチポイントの数に応じて、要素の背景色が変わります。 色の変更に関する詳細は update_background 関数を参照してください。

function pointerdown_handler(ev) {
 // The pointerdown event signals the start of a touch interaction.
 // Save this event for later processing (this could be part of a
 // multi-touch interaction) and update the background color
 // pointerdown イベントは、タッチ操作の開始を知らせます。
 // このイベントを後で処理するために保存し(これはマルチタッチ
 // 操作の一部になる可能性があります)、背景色を更新します
 push_event(ev);
 if (logEvents) log("pointerDown: name = " + ev.target.id, ev);
 update_background(ev);
}

ポインタ移動

pointermove ハンドラは、ポインタが移動したときに呼び出されます。 別のイベントタイプが発生する前に(例えば、ユーザーがポインタを移動した場合など)複数回呼び出されることがあります。

このアプリでは、ポインタの移動は、要素がこのイベントを受け取ったことを明確に視覚的に示すために、ターゲットの境界線(border)を破線(dashed)にすることで表します。

function pointermove_handler(ev) {
 // Note: if the user makes more than one "simultaneous" touch, most browsers
 // fire at least one pointermove event and some will fire several pointermoves.
 // 注: ユーザーが複数の「同時」タッチを行うと、ほとんどのブラウザーは少なくとも1つの
 // pointermove イベントを発生させ、一部はいくつかの pointermove イベントを発生させます。
 //
 // This function sets the target element's border to "dashed" to visually
 // indicate the target received a move event.
 // この関数は、ターゲットが移動イベントを受け取ったことを視覚的に示すために、
 // ターゲット要素の border を "dashed" に設定します。
 if (logEvents) log("pointerMove", ev);
 update_background(ev);
 ev.target.style.border = "dashed";
}

ポインタアップ

pointerup イベントは、ポインタが接触面から上がると発生します。 これが発生すると、そのイベントは関連付けられているイベントキャッシュから削除されます。

このアプリでは、このハンドラは pointercancelpointerleavepointerout のイベントにも使用します。

function pointerup_handler(ev) {
  if (logEvents) log(ev.type, ev);
  // Remove this touch point from the cache and reset the target's
  // background and border
  // このタッチポイントをキャッシュから削除し、
  // ターゲットの背景色と境界線をリセットします
  remove_event(ev);
  update_background(ev);
  ev.target.style.border = "1px solid black";
}

アプリの UI

アプリは、タッチ領域に <div> 要素を使用し、ログ記録を有効にするボタンとログを消去するためのボタンを提供します。

ブラウザーのデフォルトのタッチの振る舞いが、このアプリのポインタ処理をオーバーライドしないようにするために、touch-action プロパティを <body> 要素に適用しています。

<body onload="init();" style="touch-action:none">
 <div id="target1"> Tap, Hold or Swipe me 1</div>
 <div id="target2"> Tap, Hold or Swipe me 2</div>
 <div id="target3"> Tap, Hold or Swipe me 3</div>

 <!-- UI for logging/debugging -->
 <button id="log" onclick="enableLog(event);">Start/Stop event logging</button>
 <button id="clearlog" onclick="clearLog(event);">Clear the log</button>
 <p></p>
 <output></output>
</body>

その他の関数

これらの関数はアプリをサポートしますが、イベントの流れに直接は関係しません。

キャッシュ管理

これらの関数は、グローバルイベントキャッシュの evCache1evCache2evCache3 を管理します。

function get_cache(ev) {
 // Return the cache for this event's target element
 // このイベントのターゲット要素のキャッシュを返す
 switch(ev.target.id) {
   case "target1": return evCache1;
   case "target2": return evCache2;
   case "target3": return evCache3;
   default: log("Error with cache handling",ev);
 }
}

function push_event(ev) {
 // Save this event in the target's cache
 // このイベントをターゲットのキャッシュに保存する
 var cache = get_cache(ev);
 cache.push(ev);
}

function remove_event(ev) {
 // Remove this event from the target's cache
 // このイベントをターゲットのキャッシュから削除する
 var cache = get_cache(ev);
 for (var i = 0; i < cache.length; i++) {
   if (cache[i].pointerId == ev.pointerId) {
     cache.splice(i, 1);
     break;
   }
 }
}

背景色の更新

タッチ領域の背景色は次のように変わります。 アクティブなタッチがない場合は白(white)です。 1つのアクティブなタッチは黄色(yellow)です。 2つ同時のタッチはピンク(pink)で、3つ以上同時のタッチは水色(lightblue)です。

function update_background(ev) {
 // Change background color based on the number of simultaneous touches/pointers
 // currently down:
 // 現在ダウンしている同時タッチ/ポインタの数に基づいて
 // 次のように背景色を変更します
 //   white - target element has no touch points i.e. no pointers down
 //   white - ターゲット要素にタッチポイントがない。 つまり、ポインタのダウンがない。
 //   yellow - one pointer down
 //   yellow - 1つのポインタのダウン
 //   pink - two pointers down
 //   pink - 2つのポインタのダウン
 //   lightblue - three or more pointers down
 //   lightblue - 3つ以上のポインタのダウン
 var evCache = get_cache(ev);
 switch (evCache.length) {
   case 0:
     // Target element has no touch points
     // ターゲット要素にタッチポイントがない
     ev.target.style.background = "white";
     break;
   case 1:
     // Single touch point
     // 単独のタッチポイント
     ev.target.style.background = "yellow";
     break;
   case 2:
     // Two simultaneous touch points
     // 2つ同時のタッチポイント
     ev.target.style.background = "pink";
     break;
   default:
     // Three or more simultaneous touches
     // 3つ以上同時のタッチ
     ev.target.style.background = "lightblue";
 }
}

イベントログ

これらの関数は、アプリのウィンドウへのイベント活動の送信に使用されます(デバッグとイベントの流れに関する学習をサポートするため)。

// Log events flag
// イベントログフラグ
var logEvents = false;

function enableLog(ev) {
  logEvents = logEvents ? false : true;
}

function log(name, ev) {
  var o = document.getElementsByTagName('output')[0];
  var s = name + ": pointerID = " + ev.pointerId +
                " ; pointerType = " + ev.pointerType +
                " ; isPrimary = " + ev.isPrimary;
  o.innerHTML += s + "
";
}

function clearLog(event) {
 var o = document.getElementsByTagName('output')[0];
 o.innerHTML = "";
}