對齊的輸入事件

戴夫塔普斯卡
Dave Tapuska

重點摘要

  • Chrome 60 會降低事件頻率,藉此改善影格時間的一致性,藉此減少資源浪費。
  • Chrome 58 版中引入的 getCoalescedEvents() 方法提供全部的事件資訊。

在網路上提供流暢的使用者體驗,是非常重要的一環。從接收輸入事件到視覺呈現實際更新之間的時間相當重要,因此通常執行較少工作是很重要的。在過去幾個版本中,我們拖慢了這類裝置的輸入延遲時間。

為了兼顧流暢性與效能,在 Chrome 60 版中,我們將做出一項變更,讓這些事件的發生頻率更低,同時增加所提供的資訊精細程度。就像 Jelly Bean 推出並推出 Choreographer 來對齊 Android 系統輸入方式一樣,我們也已在所有平台上為網路提供對齊影格的輸入內容。

但有時候,你需要更多事件。因此,在 Chrome 58 版中,我們實作名為 getCoalescedEvents() 的方法,讓應用程式擷取指標的完整路徑,即使收到的事件較少。

我們先來談談事件頻率。

降低活動頻率

以下將說明一些基本概念:觸控螢幕以 60-120Hz 的頻率提供輸入,滑鼠則是以 100Hz 的輸入方式 (但可能不限於 2000 Hz) 輸入輸入內容。然而,螢幕的一般刷新率卻是 60Hz。但這代表什麼意思?這表示我們接收輸入內容的速率高於實際更新螢幕的頻率。讓我們來看看開發人員工具的效能時間軸,瞭解簡易畫布繪製應用程式的效能時間軸。

在下圖中,停用 requestAnimationFrame() 對齊輸入選項時,您會看到每個影格的多個處理區塊,而影格時間不一致。黃色小區塊表示的命中測試,例如 DOM 事件的目標、分派事件、執行 JavaScript、更新懸停節點,並可能重新計算版面配置和樣式。

顯示影格時間不一致的效能時間軸

為什麼我們要進行額外的工作,不會造成任何視覺更新?在理想情況下,我們不希望任何最終對使用者造成益處的工作。自 Chrome 60 版起,輸入管道會延遲分派連續事件 (wheelmousewheeltouchmovepointermovemousemove),並在發生 requestAnimationFrame() 回呼之前分派這些事件。在下圖 (須啟用這項功能) 中,您會看到影格時間更加一致,處理事件的時間也較少。

我們持續在 Canary 和開發人員版中啟用這項功能,並發現命中測試的執行次數減少了 35%,因此主要執行緒可以更頻繁地執行。

請特別注意,網頁開發人員應注意,發生的任何獨立事件 (例如 keydownkeyupmouseupmousedowntouchstarttouchend) 都會連同任何待處理事件一起分派,並保留相對順序。啟用這項功能後,大部分的工作都會串流至一般的事件迴圈流程,提供一致的輸入間隔。這樣即可實現透過 scrollresize 事件內嵌的連續事件,這些事件已簡化至 Chrome 的事件迴圈流程。

顯示影格時間相對一致的效能時間軸。

我們發現,大多數取用這類事件的應用程式都沒有使用頻率較高的應用程式。多年來,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()) 不會對父項事件產生任何影響。