ポインターイベント
=========

今日のウェブコンテンツの多くは、ユーザーのポインティング機器がマウスであることを前提としています。しかし、多くの端末がペンやスタイラス、タッチ面のような他の種類のポインティング入力機器に対応しているので、既存のポインティング機器のイベントモデルへの拡張が必要であり、そしてポインターイベントはその必要性に応じます。

**メモ:** ポインターイベントはウェブワーカーでは*利用できません*。

ポインターイベントは、ポインティング機器に対して発生する DOM イベントです。 これらは、マウス、ペンやスタイラス、（1 本以上の指でなどの）タッチなどのポインティング入力機器を処理する単一の DOM イベントモデルを作成するように設計されています。

ポインターは、特定の画面座標セットをターゲットにできるハードウェアにとらわれない機器です。 ポインターに単一のイベントモデルを使用すると、ウェブサイトやウェブアプリの作成が簡単になり、ユーザーのハードウェアに関係なく優れた使い勝手を提供できます。しかし、機器固有の処理が必要なシナリオのために、ポインターイベントはイベントを生成した機器の種類を調べるための `pointerType` プロパティを定義しています。

一般的なポインター入力を処理するために必要なイベントは、`マウスイベント`に似ています（`mousedown`/`pointerdown`、`mousemove`/`pointermove` など）。 したがって、ポインターイベント種別は意図的にマウスイベント種別と似せています。

さらに、ポインターイベントには、マウスイベントに存在する通常のプロパティ（クライアント座標、ターゲット要素、ボタンの状態など）、およびその他の入力形式のための新しいプロパティ（圧力、接触ジオメトリー、傾きなど）が含まれています。 実際、`PointerEvent` インターフェイスはすべての `MouseEvent` のプロパティを継承しているため、マウスイベントからポインターイベントへのコンテンツの移行は容易です。

用語
--

### アクティブボタン状態

ポインターが `buttons` プロパティに対してゼロ以外の値を持つ場合の条件。 例えば、ペンの場合は、ペンがデジタイザーと物理的に接触しているか、ホバー中に少なくとも 1 つのボタンが押されているときです。

### アクティブポインター

イベントを生成できる任意のポインターの入力機器。 ポインターがそれ以上のイベントを生成する可能性がある場合、ポインターはアクティブと見なされます。 例えば、ペンが持ち上げられたり動かされたりすると追加のイベントが発生する可能性があるため、ペンがダウン状態であるとアクティブであると見なされます。

### デジタイザー

接触を検出できる表面を備えた検知機器。 最も一般的な検知機器は、ペン、スタイラス、または指などの入力機器からの入力を検知することができるタッチ対応画面です。 検知機器の中には、入力機器の近接を検出できるものもあり、その状態をマウスにならってホバーと表現します。

### ヒットテスト

ブラウザーがポインターイベントのターゲット要素を決定するために使用するプロセス。 通常、これはポインターの位置と、画面媒体上の文書内の要素の視覚的なレイアウトを考慮して決定されます。

### ポインター

画面上の特定の座標（または座標のセット）をターゲットにできる、入力機器のハードウェアにとらわれない表現。 ポインター入力機器の例としては、マウス、ペン/スタイラス、タッチ接触などがあります。

### ポインターキャプチャ

ポインターキャプチャでは、ポインターイベントをポインターの位置による通常のヒットテストの結果以外の特定の要素にターゲットしなおすことができます。

### ポインターイベント

ポインターに対して発生した DOM `イベント`。

インターフェイス
--------

主なインターフェイスは `PointerEvent` インターフェイスで、これにはコンストラクターといくつかのイベント種別および関連するグローバルイベントハンドラーがあります。

この標準には、 `Element` および `Navigator` インターフェイスの拡張も含まれています。

以下の項では、各インターフェイスとプロパティについて簡単に説明します。

### PointerEvent インターフェイス

`PointerEvent` インターフェイスは `MouseEvent` インターフェイスを拡張したもので、以下のプロパティがあります。（以下のプロパティはすべて 読取専用  です。）

`pointerId`

イベントの原因となっているポインターの一意の識別子。

`width`

ポインターの接触ジオメトリーの幅（X 軸上の大きさ、CSS ピクセル単位）。

`height`

ポインターの接触ジオメトリーの高さ（Y 軸上の大きさ、CSS ピクセル単位）。

`pressure`

0 から 1 までの範囲のポインター入力の正規化された圧力。 ここで、0 と 1 は、それぞれハードウェアが検出できる最小圧力と最大圧力を表します。

`tangentialPressure`

ポインター入力の正規化された接線圧力（バレル圧力またはシリンダー応力（cylinder stress）とも呼ばれます）は -1 から 1 の範囲で、0 はコントロールの中立位置です。

`tiltX`

Y-Z 平面と、ポインター（ペン/スタイラスなど）の軸と Y 軸の両方を含む平面との間の平面角度（度単位で、 `-90` から `90` の範囲）。

`tiltY`

X-Z 平面と、ポインター（ペン/スタイラスなど）の軸と X 軸の両方を含む平面との間の平面角度（度単位で、 `-90` から `90` の範囲）。

`twist`

ポインター（ペン/スタイラスなど）の長軸を中心とした時計回りの回転の角度（`0` から `359` の範囲の値）。

`pointerType`

イベントの原因となった機器タイプ（マウス、ペン、タッチなど）を示します。

`isPrimary`

ポインターがこのポインタータイプの主ポインターを表すかどうかを示します。

### イベント種別とグローバルイベントハンドラー

ポインターイベントには 10 のイベント種別があり、そのうち 7 つ（`down`、`up`、`move`、`over`、`out`、`enter`、`leave`）はマウスイベントと同等の意味を持ちます。

以下は、各イベント種別の簡単な説明です。

| イベント | 説明 |
| --- | --- |
| `pointerover` | ポインターが要素のヒットテスト領域内に移動したときに発生します。 |
| `pointerenter` | ポインターが要素またはその子孫の 1 つのヒットテスト領域内に移動したときに発生します。 これには、ホバーに対応していない機器からの `pointerdown` イベントの結果も含まれます（`pointerdown` を参照）。 |
| `pointerdown` | ポインターが*アクティブボタン状態*になったときに発生します。 |
| `pointermove` | ポインターが座標を変更したときに発生します。 また、ポインターの状態の変化がこれ以外のイベントで報告できない場合もこのイベントが使われます。 |
| `pointerup` | ポインターが*アクティブボタン状態*でなくなったときに発生します。 |
| `pointercancel` | ブラウザーは、ポインターがイベントを生成できなくなったと判断した場合（例えば、関連機器が無効になった場合）、このイベントを発行します。 |
| `pointerout` | いくつかの理由で発生します。ポインターが要素のヒットテスト領域外に移動した場合、ホバーに対応していない機器で pointerup イベントが発生した場合（pointerup を参照）、 `pointercancel` イベントの発生後（`pointercancel` を参照）、ペン/スタイラスがデジタイザーで検出可能なホバー範囲を離脱したときなどです。 |
| `pointerleave` | ポインターが要素のヒットテスト領域外に移動したときに発生します。ペン機器の場合、このイベントは、スタイラスがデジタイザーで検出可能なホバー範囲を離脱したときに発生します。 |
| `gotpointercapture` | 要素がポインターキャプチャを受け取ったときに発生します。 |
| `lostpointercapture` | ポインターに対するポインターキャプチャが解放された後に発生します。 |

### Element の拡張

`Element` インターフェイスには次の 3 つの拡張があります。

`setPointerCapture()`

このメソッドは、将来のポインターイベントの*キャプチャターゲット*として特定の要素を指定します。

`releasePointerCapture()`

このメソッドは、特定のポインターイベントに対して以前に設定された*ポインターキャプチャ*を解放（停止）します。

### Navigator の拡張

`Navigator.maxTouchPoints` プロパティは、任意の時点で対応している同時タッチポイントの最大数を決定するために使用します。

例
-

この節では、ポインターイベントのインターフェイスを使用した基本的な使い方の例を紹介します。

### イベントハンドラーの登録

この例では、特定の要素のすべてのイベント種別に対してハンドラーを登録します。


```html
<html lang="en">
  <script>
 function over\_handler(event) {}
 function enter\_handler(event) {}
 function down\_handler(event) {}
 function move\_handler(event) {}
 function up\_handler(event) {}
 function cancel\_handler(event) {}
 function out\_handler(event) {}
 function leave\_handler(event) {}
 function gotcapture\_handler(event) {}
 function lostcapture\_handler(event) {}

 function init() {
 const el = document.getElementById("target");
 // ポインターイベントハンドラーの登録
 el.onpointerover = over\_handler;
 el.onpointerenter = enter\_handler;
 el.onpointerdown = down\_handler;
 el.onpointermove = move\_handler;
 el.onpointerup = up\_handler;
 el.onpointercancel = cancel\_handler;
 el.onpointerout = out\_handler;
 el.onpointerleave = leave\_handler;
 el.gotpointercapture = gotcapture\_handler;
 el.lostpointercapture = lostcapture\_handler;
 }
 </script>
  <body onload="init();">
    <div id="target">Touch me…</div>
  </body>
</html>
```

### イベントのプロパティ

この例では、ポインターイベントのすべてのプロパティにアクセスする方法を示します。


```html
<html lang="en">
  <script>
 const id = -1;

 function process\_id(event) {
 // イベントの識別子に基づいて、このイベントを処理する
 }
 function process\_mouse(event) {
 // マウスポインターイベントを処理する
 }
 function process\_pen(event) {
 // ペンポインターイベントを処理する
 }
 function process\_touch(event) {
 // タッチポインターイベントを処理する
 }
 function process\_tilt(tiltX, tiltY) {
 // 傾斜データハンドラー
 }
 function process\_pressure(pressure) {
 // 圧力ハンドラー
 }
 function process\_non\_primary(event) {
 // 非主ハンドラー
 }

 function down\_handler(ev) {
 // タッチポイントの接触面積を計算する
 const area = ev.width \* ev.height;

 // キャッシュされた id とこのイベントの id を比較し、それに応じて処理する
 if (id === ev.identifier) process\_id(ev);

 // 適切なポインタータイプのハンドラーを呼び出す
 switch (ev.pointerType) {
 case "mouse":
 process\_mouse(ev);
 break;
 case "pen":
 process\_pen(ev);
 break;
 case "touch":
 process\_touch(ev);
 break;
 default:
 console.log(`pointerType ${ev.pointerType} には対応していません`);
 }

 // 傾斜ハンドラーを呼び出す
 if (ev.tiltX !== 0 && ev.tiltY !== 0) process\_tilt(ev.tiltX, ev.tiltY);

 // 圧力ハンドラーを呼び出す
 process\_pressure(ev.pressure);

 // このイベントが主ではない場合は、非主ハンドラーを呼び出す
 if (!ev.isPrimary) process\_non\_primary(ev);
 }

 function init() {
 const el = document.getElementById("target");
 // pointerdown ハンドラーの登録
 el.onpointerdown = down\_handler;
 }
 </script>
  <body onload="init();">
    <div id="target">Touch me…</div>
  </body>
</html>
```

主ポインターの決定
---------

シナリオによっては、複数のポインター（例えば、タッチ画面とマウスの両方を備えた機器）やポインターが複数の接触点に対応している（例えば、複数の指でのタッチに対応しているタッチ画面）ことがあります。 アプリは、`isPrimary` プロパティを使用して、各ポインタータイプの*アクティブポインター*のセットの中からマスターポインターを識別できます。 アプリが主ポインターのみに対応したい場合は、主ではないすべてのポインターイベントを無視することできます。

マウスの場合、ポインターは 1 つだけなので、常に主ポインターになります。 タッチ入力の場合、他にアクティブなタッチがなかったときにユーザーが画面にタッチした場合、ポインターは主と見なされます。ペンとスタイラスの入力では、他のアクティブなペンが画面に接触していないときにユーザーのペンが最初に画面に接触した場合、ポインターは主と見なされます。

ボタンの状態の判断
---------

マウスやペンなどの一部のポインター機器は複数のボタンに対応しており、ボタンの押下で*和音を弾く*ことができます。 すなわち、ポインター機器上の別のボタンが既に押されている間に、追加のボタンを押すことができます。

ボタンが押された状態を判断するために、ポインターイベントは（`PointerEvent` の継承元の）`MouseEvent` インターフェイスの `button` プロパティおよび `buttons` プロパティを使用します。

次の表は、さまざまな機器ボタンの状態に対応する `button` および `buttons` の値を示しています。

| 機器のボタンの状態 | button | buttons |
| --- | --- | --- |
| 最後のイベント以降、ボタンもタッチ/ペンの接触も変化がなかった | `-1` | — |
| ボタンを押さずにマウスを動かした、ボタンを押さずにホバー中にペンを動かした | — | `0` |
| マウスの左ボタン、タッチ接触、ペン接触 | `0` | `1` |
| マウスの中ボタン | `1` | `4` |
| マウスの右ボタン、ペンのバレルボタン | `2` | `2` |
| マウスの X1 （戻る）ボタン | `3` | `8` |
| マウスの X2 （進む）ボタン | `4` | `16` |
| ペンの消しゴムボタン | `5` | `32` |

**メモ:** `button` プロパティは、ボタンの状態の変化を示していることに注意してください。ただし、タッチの場合のように、 1 つのイベントに伴って複数のイベントが発生する場合は、それらはすべて同じ値になります。

ポインターキャプチャ
----------

ポインターキャプチャは、特定のポインターイベントに対するイベントを、ポインターの位置で通常のヒットテストを行う代わりに、特定の要素に再ターゲットすることを可能にします。これは、ポインターデバイスの接点が要素から（スクロールなどで）移動しても、要素がポインターイベントを受信し続けることを保証するために使用できます。

次の例では、要素にポインターキャプチャを設定しています。


```html
<html lang="en">
  <script>
 function downHandler(ev) {
 let el = document.getElementById("target");
 // 要素 'target' はそれ以上のイベントを受信/キャプチャします
 el.setPointerCapture(ev.pointerId);
 }

 function init() {
 let el = document.getElementById("target");
 el.onpointerdown = downHandler;
 }
 </script>
  <body onload="init();">
    <div id="target">Touch me…</div>
  </body>
</html>
```


次の例は、（`pointercancel` イベントが発生したときに）ポインターキャプチャを解放しています。 `pointerup` イベントや `pointercancel` イベントが発生すると、ブラウザーはこれを自動的に行います。


```html
<html lang="en">
  <script>
 function downHandler(ev) {
 let el = document.getElementById("target");
 // 要素 "target" はそれ以上のイベントを受信/キャプチャします
 el.setPointerCapture(ev.pointerId);
 }

 function cancelHandler(ev) {
 let el = document.getElementById("target");
 // ポインターキャプチャを解放する
 el.releasePointerCapture(ev.pointerId);
 }

 function init() {
 let el = document.getElementById("target");
 // pointerdown と pointercancel のハンドラーの登録
 el.onpointerdown = downHandler;
 el.onpointercancel = cancelHandler;
 }
 </script>
  <body onload="init();">
    <div id="target">Touch me…</div>
  </body>
</html>
```

touch-action プロパティ
------------------

CSS の `touch-action` プロパティは、ブラウザーが既定の（*ネイティブの*）タッチの振る舞い（ズームやパンなど）を領域に適用するかどうかを指定するために使用します。 このプロパティは、置換されていないインライン要素、テーブル行、行グループ、テーブル列、および列グループを除くすべての要素に適用できます。

`auto` の値は、ブラウザーがその既定のタッチの振る舞いを（指定された領域に）自由に適用できることを意味し、`none` の値はブラウザーのその領域に対する既定のタッチの振る舞いを無効にします。 値 `pan-x` および `pan-y` は、指定された領域で始まるタッチがそれぞれ水平スクロールおよび垂直スクロール専用であることを意味します。 値 `manipulation` は、ブラウザーがその要素から始まるタッチをスクロールとズームのみを考慮していることを意味します。

次の例では、ブラウザーの既定のタッチの振る舞いは `div` 要素に対して無効になります。


```html
<html lang="en">
  <body>
    <div style="touch-action:none;">Can't touch this…</div>
  </body>
</html>
```


次の例では、一部の `button` 要素では既定のタッチの振る舞いが無効になります。


```css
button#tiny {
  touch-action: none;
}
```


次の例では、`target` 要素にタッチすると、水平方向にのみパンします。


```css
#target {
  touch-action: pan-x;
}
```

マウスイベントとの互換性
------------

ポインターイベントのインターフェイスを使用すると、アプリはポインター対応機器で高度なユーザーエクスペリエンスを作成できますが、現実での今日のウェブコンテンツの大部分はマウス入力でのみ機能するように設計されています。 その結果、ブラウザーがポインターイベントをサポートしていても、マウスのみの入力が直接変更せずにそのまま機能することを前提として、ブラウザーはマウスイベントを処理する必要があります。 理想的には、ポインター対応アプリはマウス入力を明示的に処理する必要はありません。 ただし、ブラウザーはマウスイベントを処理する必要があるため、対処する必要がある互換性の問題がいくつかあるかもしれません。 この節では、ポインターイベントとマウスイベントの相互作用、およびアプリ開発者への影響について説明します。

ブラウザーは、*マウスベースのコンテンツとの互換性のために、汎用ポインター入力をマウスイベントにマッピングすることができます*。 このイベントのマッピングは、*互換性マウスイベント*と呼ばれます。 作成者は `pointerdown` イベントをキャンセルすることで特定の互換性マウスイベントの生成を防ぐことができますが、次の点に注意してください。

* マウスイベントは、ポインターがダウンしているときにのみ防ぐことができます。
* ホバーしているポインター（例えばボタンが押されていないマウス）は、それらのマウスイベントを防ぐことができません。
* `mouseover`、`mouseout`、`mouseenter`、`mouseleave` の各イベントが防止されることはありません（ポインターがダウンしている場合でも）。
ベストプラクティス
---------

ポインターイベントを使用する際に考慮すべき、いくつかの*ベストプラクティス*は次のとおりです。

* イベントハンドラーで行われる作業量を最小限に抑えます。
* イベントハンドラーを特定のターゲット要素に追加します（ドキュメント全体またはドキュメントツリーの上位のノードではなく）。
* ターゲット要素（ノード）は、（通常は指で触れた）最大接触表面積を収容するのに十分な大きさであるべきです。 ターゲット領域が小さすぎる場合、それに触れると隣接する要素に対して他のイベントが発生する可能性があります。
仕様書
---

| Specification |
| --- |
| Pointer Events  |

ブラウザーの互換性
---------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

`CSS touch-action` プロパティに追加のいくつかの値が Pointer Events 仕様書の一部として定義されていますが、現在それらの値は実装の対応が限定されています。

デモと例
----

* タッチ/ポインターのテストとデモ（Patrick H. Lauke 著、英語）
コミュニティ
------

* ポインターイベントのワーキンググループ（英語）
* メールリスト（英語）
* W3C の #pointerevents IRC チャンネル（英語）
関連トピックとリソース
-----------

* タッチイベント標準（英語）