ウェブ上のものを指す操作は、以前は簡単でした。マウスを動かしたり、ボタンを押したりするだけでした。マウス以外のものはすべてマウスとしてエミュレートされ、デベロッパーは信頼できるものを正確に把握していました。
ただし、シンプルだからといって必ずしも良いとは限りません。時間の経過とともに、すべてがマウス(またはマウスのふりをするデバイス)である必要がなくなってきました。圧力感知や傾斜検知に対応したペンを使用すれば、驚くほど自由に創作できます。指を使えば、デバイスと手さえあれば作業できます。それに、複数の指を使うと作業がはかどります。
そのために、タッチイベントが用意されていますが、これはタッチ専用のまったく別の API であり、マウスとタッチの両方をサポートするには、2 つの別々のイベントモデルをコーディングする必要があります。Chrome 55 では、両方のモデルを統合した新しい標準であるポインタ イベントが搭載されています。
単一イベントモデル
ポインタ イベントは、ブラウザのポインタ入力モデルを統合し、タップ、ペン、マウスを 1 つのイベントセットにまとめます。次に例を示します。
document.addEventListener('pointermove',
ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
ev => console.log('The pointer is now over foo.'));
使用可能なすべてのイベントのリストは次のとおりです。マウス イベントに精通している方には、よく見慣れたものと思われます。
pointerover
|
ポインタが要素の境界ボックスに入った。
これは、ホバーをサポートしているデバイスでは直ちに、サポートしていないデバイスでは pointerdown イベントの前に発生します。 |
pointerenter
|
pointerover に似ていますが、バブルアップせず、子孫を異なる方法で処理します。仕様の詳細。
|
pointerdown
|
ポインタがアクティブなボタン状態になりました。入力デバイスのセマンティクスに応じて、ボタンが押されたか、接触が確立されています。 |
pointermove
|
ポインタの位置が変更されました。 |
pointerup
|
ポインタがアクティブなボタンの状態から離れた。 |
pointercancel
|
ポインタがイベントを送信する可能性が低いことを意味する何かが発生しました。つまり、進行中のアクションをキャンセルして、ニュートラルな入力状態に戻す必要があります。 |
pointerout
|
ポインタが要素または画面の境界ボックスから外れた。また、デバイスがホバーをサポートしていない場合は、pointerup の後にも発生します。 |
pointerleave
|
pointerout に似ていますが、バブルアップせず、子孫を異なる方法で処理します。仕様の詳細。
|
gotpointercapture
|
要素がポインタ キャプチャを受け取った。 |
lostpointercapture
|
キャプチャ中だったポインタが解放されました。 |
さまざまな入力タイプ
一般的に、ポインタ イベントを使用すると、入力に依存しない方法でコードを記述できます。異なる入力デバイスに個別のイベント ハンドラを登録する必要はありません。もちろん、ホバーの概念が適用されるかどうかなど、入力タイプの違いに注意する必要があります。異なる入力デバイスの種類を区別する場合(異なる入力に別々のコードや機能を提供する場合など)は、PointerEvent
インターフェースの pointerType
プロパティを使用して、同じイベント ハンドラ内で区別できます。たとえば、サイド ナビゲーション ドロワーをコーディングする場合、pointermove
イベントに次のロジックを設定できます。
switch(ev.pointerType) {
case 'mouse':
// Do nothing.
break;
case 'touch':
// Allow drag gesture.
break;
case 'pen':
// Also allow drag gesture.
break;
default:
// Getting an empty string means the browser doesn't know
// what device type it is. Let's assume mouse and do nothing.
break;
}
デフォルトのアクション
タッチ対応のブラウザでは、特定の操作でページのスクロール、ズーム、更新を行います。タッチイベントの場合、これらのデフォルト アクションが行われている間もイベントを受信します。たとえば、ユーザーがスクロールしている間も touchmove
は引き続き発生します。
ポインタ イベントでは、スクロールやズームなどのデフォルト アクションがトリガーされるたびに pointercancel
イベントが送信され、ブラウザがポインタを制御していることが通知されます。次に例を示します。
document.addEventListener('pointercancel',
ev => console.log('Go home, the browser is in charge now.'));
組み込みの速度: このモデルでは、タッチイベントと比較して、デフォルトでパフォーマンスが向上します。タッチイベントでは、同じレベルの応答性を実現するためにパッシブ イベント リスナーを使用する必要があります。
ブラウザが制御を取得しないようにするには、touch-action
CSS プロパティを使用します。要素で none
に設定すると、その要素で開始されたブラウザ定義のアクションがすべて無効になります。ただし、pan-x
など、よりきめ細かい制御が可能な値も多数あります。これにより、ブラウザが x 軸の動きには反応し、y 軸の動きには反応しないようにできます。Chrome 55 では、次の値がサポートされています。
auto
|
デフォルト: ブラウザはデフォルトのアクションを実行できます。 |
none
|
ブラウザはデフォルトのアクションを実行できません。 |
pan-x
|
ブラウザは、横方向のスクロールのデフォルト アクションのみを実行できます。 |
pan-y
|
ブラウザは、垂直スクロールのデフォルト アクションのみを実行できます。 |
pan-left
|
ブラウザは、水平スクロールのデフォルト アクションのみを実行でき、ページを左にパンするのみです。 |
pan-right
|
ブラウザは、横方向のスクロールのデフォルト アクションのみを実行でき、ページを右にパンすることのみが許可されます。 |
pan-up
|
ブラウザは、垂直スクロールのデフォルト アクションと、ページを上方向にパンする操作のみを実行できます。 |
pan-down
|
ブラウザは、垂直スクロールのデフォルト アクションと、ページを下にパンする操作のみを実行できます。 |
manipulation
|
ブラウザはスクロールとズームのアクションのみを実行できます。 |
ポインタ キャプチャ
mouseup
イベントが機能しない問題をデバッグして 1 時間も費やした後、ユーザーがクリック ターゲットの外側のボタンを離していることに気づいたことはありませんか?思いませんか?私だけかもしれませんね。
しかし、これまでは、この問題に対処する良い方法がありませんでした。ドキュメントに mouseup
ハンドラを設定し、アプリケーションに状態を保存して、状態を追跡することは可能です。ただし、これはきれいなソリューションではありません。特に、ウェブ コンポーネントを構築していて、すべてをきれいに分離しようとしている場合はなおさらです。
ポインタ イベントを使用すると、はるかに優れたソリューションを実現できます。ポインタをキャプチャできるため、pointerup
イベント(またはその他の捉えにくいイベント)を確実に取得できます。
const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
console.log('Button down, capturing!');
// Every pointer has an ID, which you can read from the event.
foo.setPointerCapture(ev.pointerId);
});
foo.addEventListener('pointerup',
ev => console.log('Button up. Every time!'));
ブラウザ サポート
執筆時点では、ポインタ イベントは Internet Explorer 11、Microsoft Edge、Chrome、Opera でサポートされており、Firefox では部分的にサポートされています。最新のリストは caniuse.com で確認できます。
ギャップを埋めるには、ポインタ イベント ポリフィルを使用できます。または、ランタイムでブラウザのサポートを確認することも簡単です。
if (window.PointerEvent) {
// Yay, we can use pointer events!
} else {
// Back to mouse and touch events, I guess.
}
ポインタ イベントは、段階的な拡張に最適です。初期化メソッドを変更して上記のチェックを行い、if
ブロックにポインタ イベント ハンドラを追加し、マウス/タップ イベント ハンドラを else
ブロックに移動するだけです。
ぜひお試しいただき、ご感想をお聞かせください。