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

翻譯:瘋狂的技術宅javascript

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

本文首發微信公衆號:jingchengyideng 點擊下面連接查看其它章節文章java


當輸入到達合成器

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

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

當你聽到「輸入事件」時,可能只會想到在文本框打字或鼠標單擊,但從瀏覽器的角度來看,輸入意味着來自用戶的全部動做。 鼠標滾輪滾動是輸入事件,觸摸或者鼠標移動也是輸入事件。瀏覽器

當發生相似在屏幕上的觸摸的用戶動做時,瀏覽器是最早先接收到動做的進程之一,可是瀏覽器進程只知道該動做發生的位置。由於選項卡內部的內容由渲染器進程處理,因此瀏覽器進程會把事件類型(如touchstart)及其座標發送到渲染器進程。 渲染器進程經過查找事件目標並運行附加的事件偵聽器來適當地處理事件。微信

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

圖1:經過瀏覽器進程路由到渲染器進程的輸入事件架構

合成器接收輸入事件

在上一篇文章中,咱們研究了合成器是如何經過合成柵格化圖層來平滑地處理滾動的。 若是沒有輸入事件偵聽器附加到頁面,那麼合成器線程能夠建立徹底獨立於主線程的新複合幀。 可是若是一些事件監聽器被附加到頁面上會怎樣呢? 若是須要處理事件,合成器線程將如何操做呢?app

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

瞭解非快速可滾動區域

因爲JavaScript是運行在主線程上的,因此當合成頁面時,合成器線程會標記頁面的一個區域,該區域將事件處理程序附加爲「非快速可滾動區域」。經過獲取此信息,合成器線程能夠確保在該區域中發生事件時將輸入事件發送到主線程。 若是輸入事件來自該區域以外,則合成器線程在不等待主線程的狀況下進行合成新幀。ide

圖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 點擊下面連接查看其它章節文章

相關文章
相關標籤/搜索