- 原文地址:Inside look at modern web browser (part 4)
- 原文做者:Mariko Kosaka
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:ThomasWhyne
- 校對者:llp0574 CoolRice
內部揭祕系列博客對現代瀏覽器如何處理代碼、顯示頁面展開探討。該系列博客共四篇,這是最後一篇。在上篇博客裏,咱們瞭解了 渲染進程與合成器。這裏咱們將一窺當用戶輸入行爲發生時,合成器如何繼續保障交互流暢。前端
聽到「輸入事件」這個字眼,你腦海裏閃現的恐怕只是輸入文本或點擊鼠標。但在瀏覽器眼中,輸入意味着一切用戶行爲。不單滾動鼠標滑輪是輸入事件,觸摸屏幕、滑動鼠標一樣也是用戶輸入事件。android
諸如觸摸屏幕之類用戶手勢產生時,瀏覽器進程會率先將其捕獲。然而瀏覽器進程所掌握的信息僅限於行爲發生的區域,由於標籤頁裏的內容都由渲染進程負責處理,因此瀏覽器進程會將事件類型(如 touchstart
)及其座標發送給渲染進程。渲染進程會尋至事件目標,運行其事件監聽器,妥善地處理事件。ios
圖 1:輸入事件由瀏覽器進程發往渲染進程git
圖 2:懸於頁面圖層的視圖窗口github
在上篇文章裏,咱們探討了合成器如何經過合成柵格化圖層,實現流暢的頁面滾動。若是頁面上沒有添加任何事件監聽,合成器線程會建立獨立於主線程的新合成幀。但要是頁面上添加了事件監聽呢?合成器線程又是如何得知事件是否須要處理的?web
由於運行 JavaScript 腳本是主線程的工做,因此頁面合成後,合成進程會將頁面裏添加了事件監聽的區域標記爲「非當即可滾動區」。有了這個信息,若是輸入事件發生在這一區域,合成進程能夠肯定應將其發往主線程處理。如輸入事件發生在這一區域以外,合成進程則肯定無需等待主線程,可繼續合成新幀。chrome
圖 3:非當即可滾動區輸入描述示意圖後端
web 開發中經常使用的事件處理模式是事件代理。由於事件會冒泡,因此你能夠在最頂層的元素中添加一個事件處理器,用來代理事件目標產生的任務。下面這樣的代碼,你可能見過,或許也寫過。瀏覽器
document.body.addEventListener('touchstart',
event => {
if (event.target === area) {
event.preventDefault();
}
});
複製代碼
這樣只需添加一個事件監聽器,便可監聽全部元素,的確十分省事。然而,若是站在瀏覽器的角度去考量,這等於把整個頁面都標記成了「非當即可滾動區」,意味着即使你設計的應用本沒必要理會頁面上一些區域的輸入行爲,合成線程也必須在每次輸入事件產生後與主線程通訊並等待返回。如此則得不償失,使本來能保障頁面滾動流暢的合成器沒了用武之地。bash
圖 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(); // 阻止默認的滾動行爲
/*
* 這裏設置程序執行任務
*/
}
}, {passive:: true});
複製代碼
或者,你也能夠應用 touch-action
這類 CSS 規則,徹底地將事件處理器屏蔽掉。
#area {
touch-action: pan-x;
}
複製代碼
圖 6:主線程檢查繪製記錄查詢座標 x、y 處繪製內容
合成器將輸入事件發送至主線程後,首先運行的是命中檢測。命中檢測會使用渲染進程中產生的繪製記錄數據,找出事件發生座標下的內容。
以前的文章裏,咱們探討了常見顯示屏如何以每秒 60 幀的頻率刷新,以及咱們要怎樣與其刷新頻率保持步調一致,以營造出流暢的動畫效果。而對於用戶的輸入行爲,常見觸摸屏設備的事件傳輸頻率爲每秒 60~120 次,常見鼠標設備的事件傳輸頻率爲每秒 100 次。可見,輸入事件有着比顯示屏幕更高的保真度。
若是一連串 touchmove
這樣的事件以每秒 120 次的頻率發送往主線程,那麼可能會觸發過量的命中檢測及 JavaScript 腳本執行。相形而言,咱們的屏幕刷新率則低下得多。
圖 7:大量事件涌入合成幀時間軸會形成頁面閃爍
爲了下降往主線程中傳遞過量調用,Chrome 會合並這些連續事件(如:wheel
, mousewheel
, mousemove
, pointermove
, touchmove
等),並將其延遲至下一次 requestAnimationFrame
前發送。
圖 8:相同的時間軸下事件被合而且延遲發送
全部獨立的事件,如: keydown
, keyup
, mouseup
, mousedown
, touchstart
, 及 touchend
則會當即發往主線程。
getCoalescedEvents
獲取幀內事件事件合併可幫助大多數 web 應用構建良好的用戶體驗。然而,若是你開發的是一個繪圖類應用,須要基於 touchmove
事件的座標繪製線路,那麼在你試圖畫下一根光滑的線條時,區間內的一些座標點也可能會因事件合併而丟失。這時,你可使用目標事件的 getCoalescedEvents
方法獲取事件合併後的信息。
圖 9:左爲流暢的觸摸手勢路徑、右爲事件合併後的有限路徑
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// 使用 x、y 座標畫線
}
});
複製代碼
本系列文章裏,咱們探討了不少關於 web 瀏覽器內部的工做原理。若是以前你歷來沒想過:爲何 Devtools 推薦在事件處理器上添加 {passive:true}
選項、爲何有時須在 script 標籤裏添加 async
屬性?那麼我但願這一系列文章能幫助你瞭解,爲何傳遞這些信息給瀏覽器能讓其提供更爲迅捷流暢的 web 體驗。
若是你想構建出對瀏覽器更爲友好的代碼,卻一直毫無頭緒,那麼不妨先從使用 Lighthouse 開始。Lighthouse 是個能夠幫助你審查網站工具,而且能提供頁面性能報告。性能報告會告訴你什麼地方處理得當,什麼地方有待提高。瀏覽審查列表也能讓你瞭解瀏覽器着力關注的重點所在。
對於不一樣的站點,桎梏其性能之處可能不盡相同,因此專門爲你本身的站點定製化一套性能評測方案,並擇優選取技術應用,是重中之重。Chrome 的 Devtools 團隊就 如何測試你的站點性能 撰有相關教程可供參閱。
若是你想進一步採用更多方案,Feature Policy 是一個新的 web 平臺,可在開發時爲你保駕護航。開啓 feature policy 能夠限制應用行爲,並使你遠離諸多技術弊端。舉個例子,若是你想確保應用不會阻塞解析,那麼能夠採用同步腳本方案運行應用。開啓 sync-script:'none'
後,致使解析阻塞的 JavaScript 腳本會被阻止運行。這就確保了你的代碼不會阻塞解析,瀏覽器也無須考慮暫停運行解析器。
剛踏上開發之路時,我幾乎只關注怎樣去寫代碼、怎樣提高本身的生產效率。誠然,這些事情很重要,但與此同時咱們也應當思考瀏覽器會怎麼去處理咱們書寫的代碼。現代瀏覽器一直致力探索如何提供更好的用戶體驗。書寫對瀏覽器友好的代碼,反過來也能提供友好的用戶體驗。路漫漫其修遠兮,但願咱們能攜手共進,構建出對瀏覽器更爲友好的代碼。
在此筆者誠摯感謝 Alex Russell、Paul Irish、Meggin Kearney、Eric Bidelman、Mathias Bynenes、Addy Osmani、Kinuko Yasuda、Nasko Oskov 和 Charlie Reis 等人對本系列文章初稿的校對。
你喜歡這一系列的文章嗎?如對以後文章有任何意見或建議,歡迎在下面評論區或是推特 @kosamari 裏留下您的寶貴意見。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。