TL;DR
- Chrome 60 降低事件頻率,藉此減少卡頓情形,進而改善影格時間的一致性。
- 在 Chrome 58 中推出的
getCoalescedEvents()
方法,可提供與您一貫使用的豐富事件資訊。
提供順暢的使用者體驗對網站來說十分重要。收到輸入事件與視覺效果實際更新之間的時間很重要,而且通常減少工作量很重要。在過去幾個 Chrome 版本中,我們已降低這些裝置的輸入延遲時間。
為了確保流暢度和效能,我們在 Chrome 60 中做出了一些變更,讓這些事件發生的頻率降低,同時提高提供資訊的精細程度。就像 Jelly Bean 發布時,帶來了可在 Android 上對齊輸入內容的 Choreographer,我們也將在所有平台上為網路帶來影格對齊輸入內容。
但有時您需要更多事件。因此,在 Chrome 58 中,我們實作了名為 getCoalescedEvents()
的方法,讓應用程式即使接收的事件較少,也能擷取指標的完整路徑。
首先,我們來談談事件頻率。
降低事件頻率
讓我們瞭解一些基本概念:觸控螢幕的輸入頻率為 60-120Hz,滑鼠的輸入頻率通常為 100Hz (但可達 2000Hz)。不過,螢幕的一般刷新率為 60Hz。這代表什麼意義?這表示我們收到輸入內容的速度,比實際更新螢幕的速度還快。因此,我們來看看簡單的畫布繪圖應用程式的效能時間軸。
在下圖中,當 requestAnimationFrame()
對齊輸入功能停用時,您可以看到每個影格有數個處理區塊,且影格時間不一致。黃色小方塊代表命中測試,例如 DOM 事件的目標、調度事件、執行 JavaScript、更新游標所在的節點,以及可能重新計算版面配置和樣式。
那麼,為什麼我們要做額外的工作,卻不會導致任何視覺更新?理想情況下,我們不希望做任何最終不會使使用者受惠的工作。自 Chrome 60 起,輸入管道會延遲調度連續事件 (wheel
、mousewheel
、touchmove
、pointermove
、mousemove
),並在 requestAnimationFrame()
回呼發生前立即調度。在下圖 (已啟用功能) 中,您會看到更一致的幀時間,以及更短的事件處理時間。
我們在 Canary 和 Dev 管道上啟用這項功能,並執行實驗,發現執行的命中測試次數減少了 35%,讓主執行緒更頻繁地準備好執行。
網頁開發人員應注意的重要事項是,任何發生的獨立事件 (例如 keydown
、keyup
、mouseup
、mousedown
、touchstart
、touchend
) 都會立即調度,並與任何待處理事件一起調度,以保留相對排序。啟用這項功能後,許多工作會簡化為一般事件迴圈流程,提供一致的輸入間隔。這會讓持續性事件與 scroll
和 resize
事件並列,這些事件已在 Chrome 中簡化為事件迴圈流程。
我們發現,大多數使用這類事件的應用程式都不需要更高的頻率。Android 已將事件對齊多年,因此沒有任何新內容,但網站在電腦平台上可能會遇到較少精細的事件。主執行緒的運作不順暢,一直是導致輸入流暢度中斷的問題,也就是說,每當應用程式執行作業時,您可能會看到位置跳躍,導致無法得知指標如何從一個位置移動到另一個位置。
getCoalescedEvents()
方法
如前所述,在極少數情況下,應用程式會希望知道指標的完整路徑。因此,為了修正您看到大幅跳躍和事件頻率降低的情況,我們在 Chrome 58 中推出了名為 getCoalescedEvents()
的指標事件擴充功能。以下是使用此 API 時,如何在應用程式中隱藏主執行緒的卡頓情形。
您可以存取導致事件發生的歷史事件陣列,而非接收單一事件。Android、iOS 和 Windows 的原生 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);
}
});
請注意,並非每個合併事件的屬性都會填入。由於合併的事件並未真正分派,而是只是沿途的乘客,因此不會進行命中測試。部分欄位 (例如 currentTarget
和 eventPhase
) 會保留預設值。呼叫調度相關方法 (例如 stopPropagation()
或 preventDefault()
) 不會對父項事件產生任何影響。