今後の方向性を示す

Sérgio Gomes

ウェブ上の情報を指し示すことは、かつては単純明快でした。マウスで動かしたり ボタンを押したりするだけでマウス以外のものはすべてマウスとしてエミュレートされ、デベロッパーは何をすべきか正確にわかっていました。

とはいえ、シンプルであるからといって、必ずしも良いというわけではありません。時間の経過とともに、すべてがマウスではない(またはマウスのふりをする)ことがますます重要になってきました。感圧と傾きに対応したペンを使って、創造的な自由度を大幅に高めることができます。必要なのはデバイスと手だけ。指だけでも必要なのはデバイスと手だけです。さらに、複数の指で操作できないのはなぜでしょうか。

これまでもタッチイベントを提供してきましたが、これらはタップ専用のまったく別の 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 イベントをデバッグするために、ユーザーがクリック ターゲットの外でボタンを離したことに気づくまで、イライラしたことはありませんか?わかった、もしかしたら私だけかも。

しかしこれまで、この問題にうまく対処する方法はありませんでした。もちろん、ドキュメントに 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 ブロックに移動します。

ぜひお試しいただき、ご意見やご感想をお寄せください。