Internet Explorer MVP による IE Test Drive 技術解説 : Pointer イベント

Internet Explorer ブログ (日本語版) では、これから数回にわたり Internet Explorer Test Drive に掲載されているサンプルについて、Internet Explorer の MVP の方々からご寄稿いただいた技術解説をお送りします。

第2回目は、第一回目に引き続き、HTML5.JP の管理人としてもおなじみの羽田野 太巳さんによるGesture イベントの解説です。

Pointer イベント

近年、デバイスの多様化に伴い、入力メカニズムも多様化してきました。スマートフォンの普及によって、タッチに対応したデバイスが数多く出回っています。そのため、ウェブサイトやウェブアプリケーション作りにおいては、マウスだけでなくタッチにも対応しなければいけません。

JavaScriptでは、マウスイベントを扱うことで、マウス操作に対応してきました。そして、タッチデバイスにはタッチイベントを規定したTouch Eventsと呼ばれる仕様に基づいたAPIを使うことが多いでしょう。

 

W3C Touch Events

https://www.w3.org/TR/touch-events/

 

近年では、おおむね、どのウェブサイトやアプリケーションでも、Touch Eventsを駆使してタッチ操作を実現しています。もしタッチデバイスに特化したウェブサイトやアプリケーションなら、特にこれで問題ないでしょう。実際にこれまでは、タッチデバイスと言えばスマートフォンやタブレットを指していました。パソコン向けにはマウス操作を、タッチデバイス向けにはタッチ操作を組み込んだページやアプリを別々に作ることが多かったと言えます。

ところが、Windows 8のようなOSが登場したことにより、パソコンですら、タッチパネルを装備するようになってきました。つまり、パソコン向けであろうが、マウスとタッチの両方を同時にサポートする必要が出てきたのです。加えて、ペン入力も今後は重要になってくるでしょう。ここで言うペンとは、単にタッチパネルを指で操作する代わり使うスタイラスペンではなく、ペン圧やペンの傾きなども検知できる入力メカニズムのことです。マウス、タッチ、ペンそれぞれが独自のイベントを持っていたとしたら、それらを同時にサポートするのは非常に面倒です。

タッチデバイスやペンデバイスにおいても、ある程度はマウスイベントで対応することができます。しかし、マウスイベントからは、入力デバイスの種類を判定することはできません。また、タッチデバイスにおいては、複数の指で同時にタッチする操作も存在します。このような操作に関しては、マウスイベントだけでは扱うことができません。

このような問題を解決するためにMicrosoft社はPointer Eventsと呼ばれるAPIをInternet Explorer 10に実装しました。そして、Microsoft社は、このAPIをウェブ標準とすべく、W3Cに提案しています。

 

W3C Pointer Events

https://dvcs.w3.org/hg/pointerevents/raw-file/tip/pointerEvents.html

 

本記事では、Internet Explorer 10に実装されているPointer Eventsの使い方について詳しく見ていきます。Pointer Eventsを確かめるには、Internet Explorer Test Driveに公開されているTouch Effectsというデモを試すのが良いでしょう。

 

Internet Explorer Test Drive - Touch Effects

https://ie.microsoft.com/testdrive/Graphics/TouchEffects/

 

指を三本タッチしたときの描画結果

このデモでは、タッチデバイスであれば、指で画面をタッチすると、タッチした位置を基準に粒子が動き出します。もちろん、マウスクリックでも、ペンタッチでも動作します。マウスやペンでは、一点しかタッチできませんが、タッチデバイスであれば、そのデバイスが対応する最大タッチ数までのタッチに同時に反応します。

本記事では、このデモのうち、タッチ操作に使われるPointerイベントにフォーカスして、サンプルを通して、その使い方を解説します。

 

Pointer Eventsの概要

Pointer Eventsの特徴は、マウス、タッチ、ペンを包括的に扱うためのイベントを定義した点です。どの入力デバイスを使っても、同様の操作であれば、同じイベントが発生します。さらに、発生したイベントから、入力デバイスの種類の判定や、入力デバイスに特化した詳細情報を取り出すことができます。

Pointer Eventsで定義されているイベントのうち、いくつかを紹介しましょう。

Pointer Eventsのイベント

イベント名

説明

MSPointerDown

指がタッチパネルに触れた、または、マウスのボタンを押した、または、ペンがデジタイザーに触れたときに発生します。

MSPointerHover

ペンをデジタイザーに触れずに動かしたときに発生します。タッチ デバイスとマウス  デバイスでは発生しません。

MSPointerMove

指をタッチパネルに触れたまま動かしたとき、または、マウスのボタンを押さない状態でカーソルを動かしたとき、または、ペンをデジタイザーに触れた状態で動かしたときに発生します。

MSPointerOut

指をタッチパネルに触れたままで動かした状態で、または、マウスのボタンを押しているかどうかにかかわらずマウスのカーソルを動かしている状態で、または、ペンがデジタイザーに触れているかどうかにかかわらずペンを動かしている状態で、それらのポインターが要素の境界を越えて中から外に出たときに発生します。

MSPointerOver

指をタッチパネルに触れたままで動かした状態で、または、マウスのボタンを押しているかどうかにかかわらずマウスのカーソルを動かしている状態で、または、ペンがデジタイザーに触れているかどうかにかかわらずペンを動かしている状態で、それらのポインターが要素の境界を超えて外から中に入ったときに発生します。

MSPointerUp

指をタッチパネルから離した、または、マウスのボタンを離した、または、ペンをデジタイザーから離したときに発生します。

MSPointerCancel

システムがポインターを破棄したときに発生します。

 

次のコードは、html要素で発生するMSPointerDownイベントを捕捉します

var html_el =
document.querySelector("html");

html_el.addEventListener("MSPointerDown",
function(e) {

    // イベントが発生したときの処理

}, false);

 

PointerEventsのイベントが発生したときに呼び出されるコールバック関数には、MSPointerEventオブジェクト(変数e)が引数に与えられます。このオブジェクトから、イベントに関するさまざまな情報を取得することができます。ここでは、MSPointerEventオブジェクトのプロパティのいくつかを紹介しましょう。

MSPointerEventオブジェクトのプロパティ

プロパティ

説明

isPrimary

プライマリーポインターならtrueを、そうでなければfalseを返します。プライマリーポインターとは、最初の接触を意味します。たとえば、二本の指でタッチした場合、必ず一方が最初に接触するはずです。この最初の接触ではtrueを、二本目の接触ではfalseを返します。マウスの場合は常にtrueを返します。

pointerId

タッチ、マウス、ペンの接触の一意的な識別子を数値で返します。

pointerType

イベントのソースがタッチ、ペン、マウスのどれに相当するのかを数値で返します。2はタッチ、3はペン、4はマウスを表します。

pressure

ペン圧を0~1の範囲の数値で返します。指でタッチした場合や、マウスのボタンを押した場合は常に0となります。

tiltX

ペンの左右の傾き角度を-90~90の範囲の数値で返します。右方向に傾けば正の値になり、左方向に方向けば負の値になります。タッチ、マウスの場合は常に0となります。

tiltY

ペンの前後の傾き角度を-90~90の範囲の数値で返します。手前に傾けば正の値になり、前方に傾けば負の値になります。タッチ、マウスの場合は常に0となります。

MSPOINTER_TYPE_TOUCH

常に2がセットされた定数です。pointerTypeプロパティの値の評価に使います。

MSPOINTER_TYPE_PEN

常に3がセットされた定数です。pointerTypeプロパティの値の評価に使います。

MSPOINTER_TYPE_MOUSE

常に4がセットされた定数です。pointerTypeプロパティの値の評価に使います。

 

次のコードは、html要素で発生するMSPointerDownイベントを捕捉し、どの入力デバイスが使われたのかを判定します。

var html_el =
document.querySelector("html");

html_el.addEventListener("MSPointerDown",
function(e) {

    if(e.pointerType ===
e.MSPOINTER_TYPE_TOUCH) {

        console.log("タッチ");

    } else if(e.pointerType ===
e.MSPOINTER_TYPE_PEN) {

        console.log("ペン");

    } else if(e.pointerType ===
e.MSPOINTER_TYPE_MOUSE) {

        console.log("マウス");

    } else {

        console.log("不明");

    }

}, false);

 

サンプル

では、Pointer Eventsを使ったサンプルを見てみましょう。次のサンプルは、画面上で検知したポインターを円で描画します。

 

サンプル

https://www.html5.jp/experiments/IE10_test_drive/pointer.html 

 

このサンプルでは、画面右下に、デバイスが受け付け可能な最大タッチ数と、認識中のタッチ数をリアルタイムに表示します。まず、画面上でマウスのボタンを押すと、ポインターの位置に赤色の円が描画されます。円には、ポインターの識別子が表示されます。

 

マウスの場合

次に指でタッチパネルをタッチします。ここでは5本の指で同時にタッチします。このサンプルでは、プライマリーポインターは赤色の円を、それ以外のポインターは青色の円を描画します。

 

タッチの場合

 

ペンデバイスの場合は、ペン圧を検知して、その圧力に応じて、円の大きさを変えます。ペン圧が大きいほど、大きな円をリアルタイムに描画します。

ペンの場合

 

さらに、ペンデバイスの場合は、ペンの傾きも検知して、それに応じて、円を立体的に傾けます。

ペンを傾けた場合

 

まず、このサンプルのHTMLとCSSをご覧ください。

HTML

<body>

 

<h1>Pointerイベントのサンプル</h1>

<p
id="touches">ポインター数: <span>0</span> / <span>0</span></p>

 

</body>

 

CSS

html {

    -ms-touch-action: none;

}

 

CSSは、実際にはさまざまなスタイルが記述されていますが、ここでは、ポイントを絞って解説します。

タッチデバイスでは、通常、二本の指でピンチ操作やストレッチ操作を行うと、ブラウザーに表示されたページ全体を縮小したり拡大したりします。このサンプルのように、タッチ操作を扱うコンテンツにおいては、このデフォルトの挙動が邪魔をします。そのため、CSSを使って、これらの挙動を無効にします。これを忘れると、タッチ操作を意図通りに扱うことができなくなりますので、注意してください。

では、スクリプトを見ていきましょう。まずは、ブラウザーがPointerイベントをサポートしているかを判定します。

Pointerイベントの実装の判定

// ポインターを表す円(div要素)を格納する変数を定義

var pointers = {};

 

// ページがロードされたときの処理

document.addEventListener("DOMContentLoaded",
function() {

    // Pointerイベントのサポートの検出

    if (window.navigator.msPointerEnabled) {

        pointerInit();

    }

}, false);

 

Pointerイベントをブラウザーが実装しているかどうかは、window.navigator.msPointerEnabledを使って判定します。このプロパティは、ブラウザーがPointerイベントを実装していればtrueを、そうでなければfalseを返します。

このサンプルでは、Pointerイベントを実装している場合にのみ処理を続け、pointerInit()関数を呼び出しています。

pointerInit()関数では、このサンプルを動作させるための下準備を行います。

pointerInit()関数

function
pointerInit() {

    // html要素のDOMオブジェクト

    var html_el =
document.querySelector("html");

    // コンテキストメニュー表示をキャンセル

   
html_el.addEventListener("contextmenu", function(e) {

        e.preventDefault();

    }, false);

    // マウスドラッグをキャンセル

   
html_el.addEventListener("dragstart", function(e) {

        e.preventDefault();

    }, false);

    // テキスト選択をキャンセル

   
html_el.addEventListener("selectstart", function(e) {

        e.preventDefault();

    }, false);

    // 最大タッチ数を表示

    var span_el =
document.querySelector("#touches>span:nth-child(2)");

    span_el.textContent =
window.navigator.msMaxTouchPoints;

    // html要素にPointerイベントのリスナーをセット

   
html_el.addEventListener("MSPointerDown", addPointer, false);

   
html_el.addEventListener("MSPointerMove", movePointer, false);

   
html_el.addEventListener("MSPointerUp", delPointer, false);

   
html_el.addEventListener("MSPointerCancel", delPointer,
false);

}

 

タッチ操作を扱う場合、さまざまなデフォルトアクションが操作の邪魔をしてしまいます。そのため、いくつかのデフォルトアクションをキャンセルしておきます。ここでは、html要素で発生するcontextmenuイベント、dragstartイベント、selectstartイベントのデフォルトアクションをキャンセルします。

次に、タッチデバイスの最大タッチ数を取得します。これは、window.navigator.msMaxTouchPointsプロパティから取得することができます。

最後に、html要素に、各種Pointerイベントのリスナーをセットします。ここでは、MSPointerDownイベント、MSPointerMoveイベント、MSPointerUpイベント、MSPointerCancelイベントのリスナーをセットします。

では、MSPointerDownイベントが発生したときに呼び出されるaddPointer()関数の処理を見てみましょう。

addPointer()関数

function
addPointer(e) {

    // div要素を生成して表示

    var el = document.createElement("div");

    document.body.appendChild(el);

    el.classList.add("pointer");

    if(e.isPrimary) {

        el.classList.add("primary");

    }

    el.textContent = e.pointerId;

    // 表示位置を調整

    el.style.left = (e.clientX - el.offsetWidth/2)
+ "px";

    el.style.top = (e.clientY -
el.offsetHeight/2) + "px";

    pointers[e.pointerId] = el;

    // ペンデバイスの場合にペン圧と傾きを反映

    if(e.pointerType === e.MSPOINTER_TYPE_PEN)
{

        var matrix = new MSCSSMatrix();

        el.style.msTransform = matrix.

            scale(1 + e.pressure).

            rotate(e.tiltY, e.tiltX, 0);

    }

    // タッチ数を表示

    var span_el =
document.querySelector("#touches>span:nth-child(1)");

    var touch_num =
parseInt(span_el.textContent, 10) + 1;

    span_el.textContent = touch_num;

}

 

この関数では、円を生成描画するために、まず、div要素を生成しています。class属性のトークンとして "pointer" を加えることで、スタイルを適用させます。これによって、円が青色でレンダリングされます。

MSPointerEventオブジェクト(変数e)のisPrimaryプロパティから、該当のポインターがプライマリーポインターかどうかを判定することができます。ここでは、もしプライマリーポインターであれば、class属性のトークンに
"primary" を加えることで、スタイルを適用させます。これによって、円が赤色でレンダリングされます。

最後に円を表すdiv要素の位置を調整します。MSPointerEventオブジェクト(変数e)は、マウスイベントのイベントオブジェクトで得られる情報と同じ情報も得られます。そのため、ポインターの座標は、e.clientXプロパティとe.clientYプロパティから得られます。

このサンプルでは、ペンデバイスの場合に限り、ペン圧とペンの傾きをレンダリングに反映させます。ペンデバイスかどうかは、e.pointerTypeプロパティを使って判定します。ペン圧はe.pressureプロパティから、ペンの傾きはe.tiltXプロパティとe.tiltYプロパティから得られますので、これらの値を、CSS Transformsを使って、スタイルとして適用します。ここでは、CSSのプロパティ値を文字列としてセットするのではなく、MSCSSMatrixオブジェクトを使っています。

では次に、MSPointerMoveイベントが発生したときに呼び出されるmovePointer()関数の処理を見てみましょう。

movePointer()関数

function
movePointer(e) {

    var el = pointers[e.pointerId];

    if(!el) { return; }

    // 表示位置を調整

    el.style.left = (e.clientX -
el.offsetWidth/2) + "px";

    el.style.top = (e.clientY -
el.offsetHeight/2) + "px";

    // ペンデバイスの場合にペン圧と傾きを反映

    if(e.pointerType === e.MSPOINTER_TYPE_PEN)
{

        var matrix = new MSCSSMatrix();

        el.style.msTransform = matrix.

            scale(1 + e.pressure).

            rotate(e.tiltY, e.tiltX, 0);

    }

}

 

MSPointerMoveイベントは、ポインターに何かしらの変化があれば、連続して発生します。ポインターの移動だけでなく、ペン圧の変化などにも反応します。この関数では、前述のaddPointer()と同様に、該当のポインターを表すdiv要素の位置調整を行います。そして、ペンデバイスの場合に限り、ペン圧とペンの傾きをレンダリングに反映させます。

最後に、MSPointerUpイベントとMSPointerCancelイベントが発生したときに呼び出されるdelPointer()関数を見てみましょう。

delPointer()関数

function
delPointer(e) {

    var el = pointers[e.pointerId];

    if(!el) { return; }

    el.parentNode.removeChild(el);

    el = undefined;

    delete pointers[e.pointerId];

    // タッチ数を表示

    var span_el =
document.querySelector("#touches>span:nth-child(1)");

    var touch_num =
parseInt(span_el.textContent, 10) - 1;

    span_el.textContent = touch_num;

}

 

この関数では、該当のポインターを表すdiv要素を削除します。そして、タッチ数の表示を一つだけ減らします。

この関数は、MSPointerUpイベントだけでなく、MSPointerCancelイベントが発生したときにも呼び出している点に注目してください。MSPointerCancelイベントは、システムがポインターを破棄したときに発生します。このイベントが発生することは希ですが、具体的には、タッチ操作中にペンによるポインターが発生したときや、最大タッチ数を超えるタッチが発生したときが該当します。システム環境によっては、必ずしも発生するとは限りません。とはいえ、発生確率はゼロではありませんので、このイベントの発生も考慮しておいた方が良いでしょう。

 

まとめ

Pointer EventsはまだW3Cに提案されたばかりです。標準化されるまでには、しばらくは時間がかかるでしょう。そのため、Internet Explorer 10に実装されているPointer Eventsは、これまでの解説で見てきたとおり、メソッド名やプロパティ名などにプレフィックスが必要です。当然のことながら、2012年12月現在では、Pointer Eventsを実装したブラウザーは、Internet Explorer 10のみです。

本記事では、プレフィックスを除けば、W3Cに提案されたPointer Eventsで定義されたAPIに限定して紹介しました。実際にPointer EventsがW3Cで勧告となるまでに多少の変更が発生するかもしれませんが、基本的には、将来的にも応用が利くでしょう。現時点では、Internet Explorer 10に限定されてしまいますが、マウス、タッチ、ペンを同時にサポートしたアプリケーション作成の参考になれば幸いです。

リファレンス

本記事のサンプル

https://www.html5.jp/experiments/IE10_test_drive/pointer.html

 

Internet Explorer Test Drive - Touch Effects

https://ie.microsoft.com/testdrive/Graphics/TouchEffects/

MSDN - MSPointerEvent object

https://msdn.microsoft.com/ja-jp/library/hh772103(v=vs.85).aspx

MSDN - MSCSSMatrix object

https://msdn.microsoft.com/ja-jp/library/windows/apps/hh453593.aspx

 

W3C Pointer Events

https://dvcs.w3.org/hg/pointerevents/raw-file/tip/pointerEvents.html

W3C CSS Transforms

https://www.w3.org/TR/css3-3d-transforms/

W3C Touch Events version 1

https://www.w3.org/TR/touch-events/

★お知らせ★

本記事を執筆されました 羽田野太巳さんが、この度 Windows 8 アプリの開発入門書を出版されました。

本書では、標準的な HTML + JavaScript だけでなく、 Windows ストア アプリ固有の Windows API、WinJS についても詳しく書かれており、MSDN ライブラリの開発チュートリアルなどを読んでいくうえで戸惑いがちなところも見事に解説されています。

現在、HTML + JavaScript の知識を持っており、これから Windows ストア アプリの開発をこれから始める人、既に初めているけどもう少し踏み込みたい人にもお勧めの 1 冊です。ぜひお手に取ってご覧ください。

 

『HTMLとJavaScriptではじめるWindowsストアアプリ開発入門』 (秀和システム 羽田野太巳・著)   
https://www.shuwasystem.co.jp/products/7980html/3572.html