輸入內容會傳送至合成器
這是 Chrome 內部檢視系列的最後一篇部落格文章,將探討 Chrome 如何處理我們的程式碼來顯示網站。在上一篇文章中,我們介紹了算繪程序,並說明瞭合成器。在本篇文章中,我們將探討在使用者輸入內容時,如何透過合成器提供流暢的互動體驗。
從瀏覽器角度看的輸入事件
當您聽到「輸入事件」時,可能只會想到在文字方塊中輸入內容或滑鼠點擊,但從瀏覽器的角度來看,輸入是指使用者的任何手勢。滑鼠滾輪捲動是輸入事件,觸控或滑鼠游標移過也是輸入事件。
當使用者手勢 (例如輕觸螢幕) 發生時,瀏覽器程序會先收到手勢。不過,由於分頁內的內容是由算繪程序處理,因此瀏覽器程序只會知道手勢發生的位置。因此,瀏覽器程序會將事件類型 (例如 touchstart
) 及其座標傳送至轉譯器程序。轉譯器程序會尋找事件目標並執行附加的事件監聽器,以便妥善處理事件。
合成器接收輸入事件
在上一篇文章中,我們探討了合成器如何透過合成區塊化圖層,流暢地處理捲動作業。如果沒有輸入事件事件監聽器附加至網頁,合成器執行緒可以建立新的合成影格,完全不受主執行緒影響。但如果某些事件事件監聽器已附加至頁面,該怎麼辦?如何判斷事件是否需要處理?
瞭解非快速捲動區域
由於執行 JavaScript 是主執行緒的工作,因此在合成網頁時,合成器執行緒會將網頁中附有事件處理常式的區域標示為「非快速捲動區域」。有了這些資訊,合成器執行緒就能確保在該區域發生事件時,將輸入事件傳送至主執行緒。如果輸入事件來自這個區域以外,則合成器執行緒會繼續合成新影格,而不需要等待主執行緒。
撰寫事件處理常式時請注意
網頁開發中常見的事件處理模式是事件委派。由於事件會向上傳遞,您可以在最上層元素中附加一個事件處理常式,並根據事件目標委派工作。您可能看過或編寫過類似以下的程式碼。
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault();
}
});
由於您只需為所有元素撰寫一個事件處理常式,因此這個事件委派模式的人體工學設計相當吸引人。不過,如果您從瀏覽器的角度查看這段程式碼,現在整個網頁都會標示為無法快速捲動的區域。也就是說,即使應用程式不關心網頁特定部分的輸入內容,合成器執行緒仍必須與主執行緒通訊,並在每次輸入事件傳入時等待該執行緒。因此,編譯器的平順捲動功能會失效。
為避免發生這種情況,您可以在事件事件監聽器中傳遞 passive: true
選項。這會向瀏覽器提示,您仍想在主執行緒中監聽事件,但合成器也可以繼續合成新影格。
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
檢查事件是否可取消
假設您在網頁中使用了一個方塊,且希望限制其捲動方向僅限於水平捲動。
在指標事件中使用 passive: true
選項,表示頁面捲動可能會順暢,但在您想要 preventDefault
以限制捲動方向時,垂直捲動可能已經開始。您可以使用 event.cancelable
方法進行檢查。
document.body.addEventListener('pointermove', event => {
if (event.cancelable) {
event.preventDefault(); // block the native scroll
/*
* do what you want the application to do here
*/
}
}, {passive: true});
或者,您也可以使用 touch-action
等 CSS 規則,完全移除事件處理常式。
#area {
touch-action: pan-x;
}
尋找事件目標
當合成器執行緒將輸入事件傳送至主執行緒時,首先要執行的是命中測試,以便找出事件目標。命中測試會使用在算繪程序中產生的繪圖記錄資料,找出事件發生點座標下方的內容。
將事件調度降到主要執行緒
在上一篇文章中,我們討論了一般螢幕每秒刷新 60 次的情形,以及我們如何保持節奏,以便呈現流暢的動畫。就輸入而言,一般觸控螢幕裝置每秒會傳送 60 到 120 次觸控事件,一般滑鼠則會傳送 100 次事件。輸入事件的精確度高於螢幕的更新頻率。
如果 touchmove
等持續性事件每秒傳送至主執行緒 120 次,則可能會觸發過多命中測試和 JavaScript 執行作業,導致螢幕更新速度變慢。
為盡量減少對主執行緒的過度呼叫,Chrome 會合併連續事件 (例如 wheel
、mousewheel
、mousemove
、pointermove
、touchmove
),並延遲調度,直到下一個 requestAnimationFrame
前。
任何離散事件 (例如 keydown
、keyup
、mouseup
、mousedown
、touchstart
和 touchend
) 都會立即調度。
使用 getCoalescedEvents
取得影格內事件
對於大多數的網頁應用程式而言,合併事件應該足以提供良好的使用者體驗。不過,如果您要建立繪圖應用程式,並根據 touchmove
座標放置路徑,可能會遺漏中間座標,無法繪製平滑的線條。在這種情況下,您可以在指標事件中使用 getCoalescedEvents
方法,取得這些合併事件的相關資訊。
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
});
後續步驟
在本系列文章中,我們介紹了網路瀏覽器的內部運作方式。如果您從未想過為何 DevTools 建議您在事件處理常式中加入 {passive: true}
,或是為何要在指令碼標記中寫入 async
屬性,希望本系列文章能讓您瞭解瀏覽器為何需要這些資訊,才能提供更快速、更流暢的網路體驗。
使用 Lighthouse
如果您想讓程式碼適合瀏覽器,但不知道從何處著手,不妨使用Lighthouse 這項工具,它可以對任何網站執行稽核,並提供報告,說明哪些做法正確,哪些需要改善。閱讀稽核清單,也可以讓您瞭解瀏覽器重視哪些項目。
瞭解如何評估成效
不同網站的效能調整方式可能不同,因此您必須評估網站成效,並決定最適合網站的做法。Chrome 開發人員工具團隊提供幾個教學課程,說明如何評估網站成效。
在網站中新增功能政策
如果您想採取額外步驟,功能政策是新的網路平台功能,可在您建構專案時提供防護機制。開啟功能政策可確保應用程式的特定行為,並避免您犯錯。舉例來說,如果您想確保應用程式絕不會阻斷剖析作業,可以讓應用程式採用同步指令碼政策。啟用 sync-script: 'none'
後,系統就會禁止執行解析器封鎖 JavaScript。這樣一來,任何程式碼都不會阻斷剖析器,瀏覽器也不必擔心會暫停剖析器。
總結
開始建構網站時,我幾乎只在乎如何編寫程式碼,以及如何提高工作效率。這些都是重要的考量,但我們也應思考瀏覽器如何處理我們撰寫的程式碼。現代瀏覽器一直在努力為使用者提供更優質的網路體驗。透過整理程式碼,讓瀏覽器運作順暢,進而提升使用者體驗。希望您能與我一起努力,讓瀏覽器更友善!
非常感謝所有審查本系列早期草稿的人員,包括 (但不限於) Alex Russell、Paul Irish、Meggin Kearney、Eric Bidelman、Mathias Bynens、Addy Osmani、Kinuko Yasuda、Nasko Oskov 和 Charlie Reis。
您喜歡這個系列嗎?如有任何問題或建議,歡迎在下方的留言區留言,或在 Twitter 上傳送訊息給 @kosamari。