深入瞭解新式網路瀏覽器 (第 4 部分)

Mariko Kosaka

輸入來源即將用於合成器

這是最後 4 篇網誌介紹文章,我們探討了 Chrome 如何處理用於顯示網站的程式碼。在上一篇文章中,我們探討了轉譯程序並瞭解合成器。在本文中,我們將介紹合成器如何在使用者輸入內容時流暢互動。

瀏覽器視角的輸入事件

聽到「輸入事件」時,您大概只會想到在文字方塊或滑鼠點擊中輸入內容,而從瀏覽器的視角,輸入則代表使用者的任何手勢。滑鼠滾輪捲動是一個輸入事件,觸控或滑鼠遊標懸停也是輸入事件。

發生螢幕觸控等使用者手勢時,瀏覽器程序是先接收該手勢的流程。不過,瀏覽器程序只能得知該手勢發生的位置,因為分頁中的內容會由轉譯器程序處理。因此,瀏覽器程序會將事件類型 (例如 touchstart) 及其座標傳送至轉譯器程序。轉譯器程序會找出事件目標,並執行附加的事件監聽器,以便妥善處理事件。

輸入事件
圖 1:透過瀏覽器程序轉送至轉譯器程序的輸入事件

合成器接收輸入事件

圖 2:可視區域懸停在頁面圖層上

在上一篇文章中,我們探討了合成器透過組合光柵化層,如何順暢處理捲動。如果頁面未附加任何輸入事件監聽器,合成器執行緒可以建立與主執行緒完全無關的新的複合頁框。但如果網頁有附加事件監聽者的話,該怎麼辦?合成器執行緒如何得知是否需要處理事件?

瞭解無法快速捲動的區域

由於執行 JavaScript 是主要執行緒的工作,因此在頁面進行複合處理時,合成器執行緒會標記某個頁面的區域,其事件處理常式附加為「Non-Fast Scrollable Region」。透過這個資訊,如果事件發生在該地區發生,合成器執行緒就能確保將輸入事件傳送至主要執行緒。如果輸入事件來自這個區域外,合成器執行緒就會儲存新影格,不必等待主執行緒。

有限的可捲動區域
圖 3:說明非快速可捲動區域的輸入內容圖表

編寫事件處理常式時請注意以下事項

網站開發的常見事件處理模式是事件委派。由於事件泡泡,因此您可以在最上層元素附加一個事件處理常式,並根據事件目標委派工作。您可能看過或編寫如下所示的程式碼。

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

由於所有元素只需要編寫一個事件處理常式,因此這個事件委派模式十分有吸引力。不過,如果您是從瀏覽器的角度查看這段程式碼,現在整個頁面都會標示為無法快速捲動的區域。這表示即使應用程式不重視頁面特定部分的輸入內容,合成器執行緒也必須與主執行緒通訊,並在每次輸入事件發生時等待。因此,合成器的流暢捲動能力將會失效。

完整頁面無法快速捲動的區域
圖 4:針對整個頁面的無法快速捲動區域說明輸入內容圖表

如要降低這種情況的發生,您可以在事件事件監聽器中傳遞 passive: true 選項。這會提示您想在主執行緒中監聽事件的瀏覽器,但合成器也可以繼續合成新頁框。

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

確認活動是否可取消

頁面捲動
圖 5:將部分頁面固定為水平捲動的網頁

假設您在網頁中有一個方塊,您希望將捲動方向限制為水平捲動。

在指標事件中使用 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;
}

尋找事件目標

點擊測試
圖 6:這個主執行緒會查看繪畫記錄,詢問 x.y Point 上繪製的內容

當合成器執行緒將輸入事件傳送至主執行緒時,首先要執行的就是用來找出事件目標的命中測試。命中測試會使用算繪程序中產生的繪製資料,找出事件發生的點座標下方。

盡量減少傳送至主執行緒的事件

在上一篇文章中,我們討論了一般螢幕如何每秒重新整理 60 次,以及如何跟上流暢的動畫播放頻率。就輸入內容而言,一般的觸控螢幕裝置每秒會傳送 60 到 120 次觸控事件,一般滑鼠每秒傳送事件 100 次。輸入事件的擬真度比螢幕可重新整理高。

如果 touchmove 等連續事件每秒傳送至主執行緒 120 次,則相較於螢幕重新整理的速度,可能會觸發過多的命中測試和 JavaScript 執行作業。

未經篩選的事件
圖 7:使影格時間軸大量的事件導致網頁卡頓

為盡量減少對主要執行緒的呼叫過多,Chrome 會聯結連續事件 (例如 wheelmousewheelmousemovepointermovetouchmove),並延遲分派時間,直到下一個 requestAnimationFrame 之前。

結盟活動
圖 8:與之前相同,但活動已共化並延遲

所有不同的事件 (例如 keydownkeyupmouseupmousedowntouchstarttouchend) 都會立即分派。

使用 getCoalescedEvents 取得影格內事件

在多數網頁應用程式中,合併的事件應該足以提供良好的使用者體驗。不過,如果您是建構應用程式,並根據 touchmove 座標放置路徑,則在座標之間可能會遺失,以繪製平滑的線條。在這種情況下,您可以在指標事件中使用 getCoalescedEvents 方法,取得這些聯合事件的相關資訊。

getCoalescedEvents
圖 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.
    }
});

後續步驟

本系列涵蓋網路瀏覽器的內部運作原理。如果您從未想過 DevTools 建議在事件處理常式中加入 {passive: true} 的原因,或者為何要在指令碼標記中編寫 async 屬性,希望這系列課程將說明瀏覽器為何需要這些資訊,才能提供更快速流暢的網頁體驗。

使用 Lighthouse

如果您想讓程式碼適合瀏覽器,卻不知道從何開始,Lighthouse 這項工具可以對任何網站進行稽核,還會提供相關報表,讓您知道哪些是採用正確做法,以及需要改善的地方。此外,您也可以瀏覽稽核清單,瞭解瀏覽器重視的內容。

瞭解如何評估成效

效能的改進因網站而異,因此請務必評估網站效能,並決定最適合網站的條件。Chrome 開發人員工具團隊提供了一些關於如何評估網站效能的教學課程。

在網站中新增功能政策

如果您希望執行額外步驟,功能政策是新的網路平台功能,可為您建構專案時提供保護。啟用功能政策可以確保應用程式的特定行為,避免犯錯。舉例來說,如果您想確保應用程式一律不會封鎖剖析功能,可以採用同步指令碼政策來執行應用程式。啟用 sync-script: 'none' 後,系統將禁止執行剖析器的 JavaScript。這樣可防止任何程式碼封鎖剖析器,而且瀏覽器也不必擔心暫停剖析器的問題。

總結

謝謝

開始建立網站時,我幾乎只關注自己撰寫程式碼的方式,以及能提高工作效率的方法。這些事項很重要,但也應該思考瀏覽器如何接收我們編寫的程式碼。新式瀏覽器持續投注心力,並持續投注心力,為使用者提供更優質的網路體驗。透過整理程式碼 讓瀏覽器享有友善的體驗 進而改善使用者體驗展信愉快!

非常感謝各位審核本系列的早期草稿,包括 (但不限於):Alex RussellPaul IrelandMeggin KearneyEric BidelmanMathias BynensAddy OsmaniKinuko1}、

你喜歡這個系列叢書嗎?如果您對日後貼文有任何疑問或建議,歡迎透過下方留言專區或 Twitter 上的 @kosamari 與我們分享您的想法。