配置された入力イベント

Dave Tapuska
Dave Tapuska

要約

  • Chrome 60 では、イベントの頻度を下げてジャンクを減らし、フレーム タイミングの一貫性を高めています。
  • Chrome 58 で導入された getCoalescedEvents() メソッドは、これまでと同じ豊富なイベント情報を提供します。

ウェブでは、スムーズなユーザー エクスペリエンスを提供することが重要です。入力イベントを受信してからビジュアルが実際に更新されるまでの時間が重要であり、一般的に、少ない作業を行うことが重要です。Chrome の過去数回のリリースで、これらのデバイスの入力レイテンシを短縮してきました。

スムーズさとパフォーマンスを重視して、Chrome 60 では、これらのイベントの発生頻度を下げながら、提供される情報の粒度を上げるように変更します。Jelly Bean のリリースで Android の入力をフレームに合わせる Choreographer が導入されたときと同様に、すべてのプラットフォームのウェブにフレームに合わせた入力が導入されます。

ただし、イベントがさらに必要な場合もあります。そのため、Chrome 58 では、getCoalescedEvents() というメソッドを実装しました。これにより、イベントの受信数が少ない場合でも、アプリケーションはポインタの完全なパスを取得できます。

まず、イベントの頻度について説明します。

イベントの頻度の低下

基本を理解しましょう。タッチスクリーンは 60 ~ 120 Hz で入力を送信し、マウスは通常 100 Hz で入力を送信します(最大 2,000 Hz まで可能です)。一方、モニターの一般的なリフレッシュ レートは 60 Hz です。具体的にどのような意味でしょうか。つまり、ディスプレイを実際に更新するレートよりも高いレートで入力を受け取ります。では、簡単なキャンバス ペイントアプリのパフォーマンス タイムラインを DevTools で確認してみましょう。

下の図は、requestAnimationFrame() アライメント入力が無効になっている場合のフレームごとの複数の処理ブロックを示しています。フレーム時間が不一致です。黄色の小さなブロックは、DOM イベントのターゲット、イベントのディスパッチ、JavaScript の実行、ホバーしたノードの更新、レイアウトとスタイルの再計算などのヒットテストを示します。

フレーム タイミングの不一致を示すパフォーマンス タイムライン

では、視覚的な更新につながらない作業を追加で行う理由は何でしょうか?最終的にユーザーにメリットがない作業は、理想的には行わないことです。Chrome 60 以降、入力パイプラインは連続イベント(wheelmousewheeltouchmovepointermovemousemove)のディスパッチを遅らせ、requestAnimationFrame() コールバックの直前にディスパッチします。下の図(この機能が有効になっている場合)では、フレーム時間がより一貫しており、イベントの処理時間が短縮されています。

Canary チャンネルと Dev チャンネルでこの機能を有効にしてテストを実施したところ、ヒットテストの実行回数が 35% 減少し、メインスレッドをより頻繁に実行できるようになりました。

ウェブ デベロッパーが注意すべき重要な点は、発生した個別のイベント(keydownkeyupmouseupmousedowntouchstarttouchend など)は、保留中のすべてのイベントとともに直ちにディスパッチされ、相対的な順序が維持されることです。この機能を有効にすると、多くの処理が通常のイベントループ フローに統合され、一貫した入力間隔が提供されます。これにより、Chrome のイベントループ フローにすでに統合されている scroll イベントと resize イベントと連続イベントが連携するようになります。

比較的一定のフレーム タイミングを示しているパフォーマンス タイムライン。

このようなイベントを使用するほとんどのアプリケーションでは、高い頻度を使用する必要はありません。Android ではすでに数年前からイベントが調整されているため、新しいイベントはありませんが、デスクトップ プラットフォームではイベントの粒度が粗くなる可能性があります。メインスレッドのジャンクによって入力の滑らかさが損なわれるという問題は常に存在していました。つまり、アプリケーションが処理を行っているときは常に位置がジャンプし、ポインタがどのように移動したかを把握できなくなります。

getCoalescedEvents() メソッド

前述のように、アプリがポインタの完全パスを知る必要があるシナリオはまれです。そのため、大きなジャンプとイベントの頻度の低下が発生するケースを修正するため、Chrome 58 で、ポインタ イベントの拡張機能である getCoalescedEvents() をリリースしました。以下は、この API を使用すると、メインスレッドのジャンクがアプリからどのように隠されるかを示した例です。

標準イベントと統合イベントの比較

単一のイベントを受信する代わりに、イベントの原因となった過去のイベントの配列にアクセスできます。AndroidiOSWindows のネイティブ SDK には、非常に類似した API が用意されており、同様の API がウェブに公開されています。

通常、描画アプリは、イベントのオフセットを確認して点を描画します。

window.addEventListener("pointermove", function(event) {
    drawPoint(event.pageX, event.pageY);
});

このコードは、イベントの配列を使用するように簡単に変更できます。

window.addEventListener("pointermove", function(event) {
    var events = 'getCoalescedEvents' in event ? event.getCoalescedEvents() : [event];
    for (let e of events) {
    drawPoint(e.pageX, e.pageY);
    }
});

統合されたイベントのすべてのプロパティが入力されるわけではありません。統合されたイベントは実際にはディスパッチされず、単に一緒に送信されるだけであるため、ヒットテストは行われません。currentTargeteventPhase などの一部のフィールドにはデフォルト値が設定されます。stopPropagation()preventDefault() などのディスパッチ関連メソッドを呼び出しても、親イベントには影響しません。