現代瀏覽器探祕(part4):事件處理

瘋狂的技術宅發表於2019-02-27

翻譯:瘋狂的技術宅

原文:developers.google.com/web/updates…

本文首發微信公眾號:jingchengyideng 點選下面連結檢視其它章節文章


當輸入到達合成器

這是關於Chrome瀏覽器內部工作原理系列的最後一篇;研究瀏覽器怎樣通過處理程式碼來顯示網站。在上一篇文章中,我們研究了渲染過程並瞭解了合成器。 在本文中,我們將分析當使用者輸入時,合成器是怎樣實現平滑互動的。

從瀏覽器的角度看輸入事件

當你聽到“輸入事件”時,可能只會想到在文字框打字或滑鼠單擊,但從瀏覽器的角度來看,輸入意味著來自使用者的所有動作。 滑鼠滾輪滾動是輸入事件,觸控或者滑鼠移動也是輸入事件。

當發生類似在螢幕上的觸控的使用者動作時,瀏覽器是最先先接收到動作的程式之一,但是瀏覽器程式只知道該動作發生的位置。因為選項卡內部的內容由渲染器程式處理,所以瀏覽器程式會把事件型別(如touchstart)及其座標傳送到渲染器程式。 渲染器程式通過查詢事件目標並執行附加的事件偵聽器來適當地處理事件。

圖1:通過瀏覽器程式路由到渲染器程式的輸入事件

圖1:通過瀏覽器程式路由到渲染器程式的輸入事件

合成器接收輸入事件

在上一篇文章中,我們研究了合成器是如何通過合成柵格化圖層來平滑地處理滾動的。 如果沒有輸入事件偵聽器附加到頁面,那麼合成器執行緒可以建立完全獨立於主執行緒的新複合幀。 但是如果一些事件監聽器被附加到頁面上會怎樣呢? 如果需要處理事件,合成器執行緒將如何操作呢?

現代瀏覽器探祕(part4):事件處理

圖2:將滑鼠懸停在頁面圖層上

瞭解非快速可滾動區域

由於JavaScript是執行在主執行緒上的,所以當合成頁面時,合成器執行緒會標記頁面的一個區域,該區域將事件處理程式附加為“非快速可滾動區域”。通過獲取此資訊,合成器執行緒可以確保在該區域中發生事件時將輸入事件傳送到主執行緒。 如果輸入事件來自該區域之外,則合成器執行緒在不等待主執行緒的情況下進行合成新幀。

圖3:輸入到非快速可滾動區域的示意圖

圖3:輸入到非快速可滾動區域的示意圖

在編寫事件處理程式時要注意

Web開發中常見的事件處理模式是事件委託。 由於事件冒泡,你可以在最頂層的元素上附加一個事件處理程式,並根據事件目標委派任務。 你可能看到過或寫過類似下面的程式碼。

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});
複製程式碼

由於你只需要為所有元素編寫一個事件處理程式,因此該事件委託模式在工程上很有吸引力。 但是如果從瀏覽器的角度來看這段程式碼,整個頁面都被標記成了非快速可滾動區域。那麼這意味著什麼呢?即使你的應用不關心頁面中某些部分的輸入,合成器執行緒也必須與主執行緒通訊,並且在每次輸入事件進入時都要等待它。因此合成器的平滑滾動能力被破壞了。

圖4:在覆蓋整個頁面的非快速可滾動區域進行輸入

圖4:在覆蓋整個頁面的非快速可滾動區域進行輸入

為了緩解這種情況,你可以在事件偵聽器中傳遞passive:true選項。 這向瀏覽器提示你仍然希望在主執行緒中監聽事件,同時合成器也可以繼續併合成新幀。

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});
複製程式碼

檢查事件是否可取消

想象一下,在頁面中有一個框,你希望僅將滾動方向限制為水平滾動。

在滑鼠事件中使用 passive:true 選項意味著可以平滑滾動頁面,但是在你想要用preventDefault 來限制滾動方向時,垂直滾動可能已經開始了。 你可以使用event.cancelable方法對這種情況進行檢查。

圖5:一個部分內容被固定為水平滾動的網頁

圖5:一個部分內容被固定為水平滾動的網頁

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});
複製程式碼

或者你可以使用CSS規則(例如touch-action)來完全消除事件處理程式。

#area {
  touch-action: pan-x;
}
複製程式碼

查詢事件目標

當合成器執行緒向主執行緒傳送輸入事件時,首先要做的是命中測試以查詢事件目標。 命中測試查詢事件發生的座標之下的內容,它使用在渲染程式中生成的繪製記錄資料來完成這一使命。

圖6:檢視繪製記錄的主執行緒詢問在x.y座標點上繪製的內容

圖6:檢視繪製記錄的主執行緒詢問在x.y座標點上繪製的內容

最小化事件傳送到主執行緒

在上一篇文章中,我們討論了我們的顯示器以每秒60次的頻率重新整理的機制,以及我們怎樣跟上節奏來獲得流暢的動畫效果。 對於輸入來說,典型的觸控式螢幕裝置每秒傳送60-120次觸控事件,而典型的滑鼠每秒傳送100次事件。 輸入事件具有比螢幕重新整理更高的保真度。

如果類似touchmove的連續事件被髮送到主執行緒120次,那麼與螢幕重新整理的速度相比,它可能會觸發過多的命中測試和JavaScript的執行。

圖7:充斥在幀時間線上的事件導致頁面閃爍

圖7:充斥在幀時間線上的事件導致頁面閃爍

為了最大限度地減少對主執行緒的過度呼叫,Chrome會合並連續事件(例如wheel, mousewheel, mousemove, pointermove, touchmove),並進行延遲排程,直到下一個 requestAnimationFrame

圖8:與上圖相同的時間線,但是正在合併和延遲事件

圖8:與上圖相同的時間線,但是正在合併和延遲事件

任何離散事件,例如 keydownkeyupmouseupmousedowntouchstart、和 touchend 都會被立即傳送。

使用 getCoalescedEvents 獲取幀內事件

對於大多數Web應用程式,合併事件應足以提供良好的使用者體驗。 但是如果要構建一個繪圖應用並根據 touchmove 座標放置路徑,則可能會在繪製平滑線時丟失中間座標。 在這種情況下,你可以在滑鼠事件中使用getCoalescedEvents方法來獲取有關這些合併事件的資訊。

圖9:左側是平滑的觸控手勢路徑,右側是合併限制路徑

圖9:左側是平滑的觸控手勢路徑,右側是合併限制路徑

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.
    }
});
複製程式碼

下一步

在本系列中,我們介紹了Web瀏覽器的內部工作原理。 如果你從未想過為什麼"開發者工具"建議在你的事件處理中新增{passive: true}或者為什麼你可以在指令碼標記中編寫async屬性,我希望本系列能夠說明為什麼瀏覽器需要這些資訊來提供更快更順暢的體驗。

使用Lighthouse

如果你想讓自己的程式碼對瀏覽器友好,但不知道從哪裡開始,可以使用Lighthouse這個網站審計工具,它為你提供一份報告,說明正在做什麼和需要改進什麼。 閱讀稽核列表還可以讓你瞭解瀏覽器關注的內容。

瞭解如何衡量效能

不同網站的效能調整可能會有所不同,因此,衡量網站的效果並確定最適合你網站的內容至關重要。 Chrome DevTools團隊沒多少關於如何衡量網站效能的教程。

向你的站點新增功能策略

功能策略是一個新的Web平臺功能,可以在你構建專案時為你提供保護。 啟用功能策略可確保應用的某些行為並防止你出錯。 例如,如果要確保應用永遠不會阻止解析,或者可以在同步指令碼策略上執行應用。 啟用 sync-script: 'none' 時,將禁止解析器阻止 JavaScript 執行。 這可以防止你的程式碼阻止解析器,並且瀏覽器也不需要擔心暫停解析器。

總結

thank you

當開始構建網站時,我幾乎只關心如何編寫程式碼以及怎樣才能幫助我提高工作效率。 這些很重要,但我們也應該考慮瀏覽器如何獲取我們編寫的程式碼。 現代瀏覽器將繼續致力於為使用者提供更好的Web體驗。 反過來通過使程式碼對瀏覽器友好,也可以改善你的使用者體驗。 希望我們一起努力追求更好的瀏覽器!


本文首發微信公眾號:jingchengyideng 點選下面連結檢視其它章節文章


相關文章