配置された入力イベント

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() コールバックが発生する直前にディスパッチされるようになります。次の図のように(この機能を有効にした場合)、フレーム時間はより一貫しており、イベントの処理時間が短縮されています。

Google は、Canary チャンネルと Dev チャンネルでこの機能を有効にしたテストを行ってきました。その結果、実施したヒットテストのヒット数が 35% 減少し、メインスレッドをより高い頻度で実行できることがわかりました。

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

比較的一貫したフレーム時間を示すパフォーマンス タイムライン。

このようなイベントを使用するアプリの大半では、高い頻度は使用できないことがわかっています。Android ではすでに何年も前からイベントの調整を行っていたため、新しい情報はありませんが、パソコン プラットフォームでは、サイトで発生するイベントの詳細度が下がる可能性があります。ジャンクが発生するメインスレッドの問題は、入力の滑らかさに支障をきたす問題を常に生じていました。つまり、アプリが作業しているたびに位置がずれることがあり、ポインタの位置間の移動方法を知ることができません。

getCoalescedEvents() メソッド

前述のように、アプリがポインタのフルパスを把握する必要があるケースはまれです。そこで、イベントが大幅に増加し、イベントの頻度が低下した場合に対応するため、Chrome 58getCoalescedEvents() というポインタ イベントの拡張機能をリリースしました。この API を使用した場合に、メインスレッド上のジャンクがアプリからどのように非表示になるかの例を以下に示します。

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

単一のイベントを受信する代わりに、イベントを発生させた過去のイベントの配列にアクセスできます。AndroidiOSWindows のネイティブ SDK にはよく似た API があり、Google は同様の 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() などのディスパッチ関連のメソッドを呼び出しても、親イベントには影響しません。