1. 程式人生 > >讓頁面滑動流暢得飛起的新特性:Passive Event Listeners

讓頁面滑動流暢得飛起的新特性:Passive Event Listeners

宣告:本文來自騰訊增值產品部官方公眾號小時光茶社,為CSDN原創投稿,未經許可,禁止任何形式的轉載。
作者:陳志興,騰訊高階工程師,11年畢業後加入騰訊,期間主要負責過QQ檔案傳輸質量優化、本地檔案共享跨網訪問、多終端訊息同步、手Q個性化業務開發、手Q WebView效能優化等。喜歡閱讀優秀的開源專案,聽聽音樂,偶爾也會打打競技類遊戲。
責編:錢曙光,關注架構和演算法領域,尋求報道或者投稿請發郵件qia[email protected],另有「CSDN 高階架構師群」,內有諸多知名網際網路公司的大牛架構師,歡迎架構師加微信qshuguang2008申請入群,備註姓名+公司+職位。

前言

圖片描述

在不久前的Google I/O 2016 Mobile Web Talk中,Google公佈了一個讓頁面滑動更流暢的新特性Passive Event Listeners。該特性目前已經整合到Chrome51版本中。

圖片描述

上圖為Chrome51上使用Passive Event Listener特性前後的效果對比圖(原視訊連結)。

從效果對比視訊中可以明顯看到,使用Passive Event Listeners特性後,頁面的滑動流暢度相對使用之前提升了很多。

看完Passive Event Listeners特性這麼給力的效果後,相信大部分童鞋腦海中都會產生以下幾個問題:

1. Passive Event Listeners是什麼?


2. 為什麼需要Passive Event Listeners?
3. Passive Event Listeners是怎麼實現的?

接下來,我們將圍繞上面的這3個問題來深入理解Passive Event Listeners特性。

Passive Event Listeners是什麼?

Passive Event Listeners是Chrome提出的一個新的瀏覽器特性:Web開發者通過一個新的屬性passive來告訴瀏覽器,當前頁面內註冊的事件監聽器內部是否會呼叫preventDefault函式來阻止事件的預設行為,以便瀏覽器根據這個資訊更好地做出決策來優化頁面效能。當屬性passive的值為true的時候,代表該監聽器內部不會呼叫preventDefault函式來阻止預設滑動行為,Chrome瀏覽器稱這型別的監聽器為被動(passive)監聽器。目前Chrome主要利用該特性來優化頁面的滑動效能,所以Passive Event Listeners特性當前僅支援mousewheel/touch相關事件。

如下面的Html程式碼中,頁面通過呼叫document.addEventListener來新增一個mousewheel事件的監聽器handler,並通過設定passive屬性的值為true來宣告監聽器handler是被動監聽mousewheel事件,即handler內部不會呼叫事件的preventDefault函式。

function handler(e) { 
    doSomething(); // do something here
 }
document.addEventListener('mousewheel', handler, {passive: true});

為什麼需要Passive Event Listeners特性?

Passive Event Listeners特性是為了提高頁面的滑動流暢度而設計的,頁面滑動流暢度的提升,直接影響到使用者對這個頁面最直觀的感受。這個不難理解,想象一下你想要滑動某個頁面瀏覽內容,當你用滑鼠滾輪或者用手指觸控式螢幕幕上下滑動的時候,頁面並沒有按你的預期進行滾動,此時你內心往往會感覺到一絲不爽,甚至想放棄該頁面。Facebook之前做了一項試驗,他們將頁面滑動的響應重新整理率從60FPS降低到30FPS的時候,發現使用者的參與度急速下降。

由前面對Passive Event Listeners特性的介紹可知,Passive Event Listenrers特性是讓Web開發者來告訴瀏覽器,當前頁面內註冊的mousewheel/touch事件監聽器是否屬於被動監聽器,以便讓瀏覽器更好地做決策來提高頁面的滑動流暢度。那麼Chrome瀏覽器為什麼需要知道是否被動監聽器這個資訊呢?瀏覽器知道這個資訊之後,它要做什麼決策呢?要回答這個問題,有必要先了解一下目前Chrome瀏覽器的執行緒化渲染框架,它是Passive Event Listeners特性的基礎。

在介紹Chrome瀏覽器的執行緒化渲染框架之前,我們先來簡單瞭解本文涉及到的Chrome瀏覽器的一些概念。

  1. 繪製(Paint):將繪製操作轉換成為影象的過程(比如軟體模式下經過光柵化生成點陣圖,硬體模式下經過光柵化生成紋理)。在Chrome中,繪製分為兩部分實現:繪製操作記錄部分(main-thread side)和繪製實現部分(impl-side)。繪製記錄部分將繪製操作記錄到SKPicture中,繪製實現部分負責將SKPicture進行光柵化轉成影象;
  2. 圖層(Paint Layer):在Chrome中,頁面的繪製是分層繪製的,頁面內容變化的時候,瀏覽器僅需要重新繪製內容變化的圖層,沒有變化的圖層不需要重新繪製;
  3. 合成(Composite):將繪製好的圖層影象混合在一起生成一張最終的影象顯示在螢幕上的過程;
  4. 渲染(Render):可以簡單認為渲染等價於繪製+合成;
  5. UI執行緒(UI Thread):瀏覽器的主執行緒,負責接收到系統派發給瀏覽器視窗的事件、資源下載等;
  6. 核心執行緒(Main/Render Thread):Blink核心及V8引擎執行的執行緒,如DOM樹構建、元素佈局、繪製(main-thread side)、JavaScript執行等邏輯在該執行緒中執行;
  7. 合成執行緒(Compositor Thread):負責影象合成的執行緒,如繪製(impl-side),合成等邏輯在該執行緒中執行。

OK,瞭解完上面的幾個概念後,我們正式開始Chrome執行緒渲染框架的介紹。

Chrome瀏覽器的執行緒化渲染框架

我們回顧一下傳統的單執行緒渲染框架,如下圖所示,核心執行緒幾乎包攬了頁面內容渲染的所有工作,如JavaScript執行,元素佈局,圖層繪製,圖層影象合成等,每項工作的執行耗時基本都跟頁面內容相關,耗時一般在幾十毫秒至幾百毫秒不等。

圖片描述

對於這種單執行緒渲染框架,存在兩個明顯的問題:

1. 流水線的執行方式,後面的工作必須等待前面工作執行完成才能處理,無法將相互獨立的工作並行處理;
2. 核心執行緒負責的工作太多且耗時,一旦遇上核心在執行耗時較長的工作,使用者的輸入事件將無法立即得到響應。

對於第1個問題,瀏覽器很難控制頁面從內容變化到佈局渲染整個過程的耗時(即新生成一幀內容的耗時),中間任何一項工作的執行都可能導致整體過程耗時變大,過大的耗時會導致頁面內容的重新整理率偏低,從而形成視覺上的卡頓。如瀏覽器收到VSync中斷訊號通知的時候,意味著頁面需要立即對內容進行渲染,但這個時候核心執行緒可能還在執行一些業務的JavaScript程式碼,導致頁面內容的渲染無法立即開始,如果頁面無法在下一個VSync中斷訊號到來之前完成對內容的渲染,則頁面會出現丟幀,即視覺體驗上的卡頓。

注:VSync訊號中斷的頻率,一般跟裝置螢幕的重新整理率對齊,比如裝置的重新整理率為60FPS(Frames Per Second),那麼大概16.67ms會觸發一下Vsync中斷訊號。Chrome瀏覽器和Android系統等都是通過VSync中斷訊號來通知頁面啟動內容的渲染(BeginFrame)。

對於第2個問題,由於核心執行緒負責的工作太多,這將導致核心執行緒經常處於忙碌狀態,無法快速處理外界的輸入訊息,表現為使用者操作了頁面,但是無法立即得到響應。

為了優化第1個問題,Chrome瀏覽器對核心執行緒負責的工作進行拆分,通過多執行緒併發處理提高渲染效率減少丟幀,如核心執行緒僅負責DOM樹構建、元素的佈局、圖層繪製記錄部分(main-thread side)、JavaScript的執行,而圖層繪製實現部分(impl-side)、圖層影象合成則是交給合成執行緒負責處理。這種多執行緒負責頁面內容的渲染框架,在Chrome中稱為執行緒化渲染框架(Threaded Compositor Architecture)。

圖片描述

如上圖所示,在Chrome的執行緒化渲染框架中,當核心執行緒完成第1幀(Frame#1)的佈局和記錄繪製操作,立即通知合成執行緒對第一幀(Frame#1)進行渲染,然後核心執行緒就開始準備第2幀(Frame#2)的佈局和記錄繪製操作。由此可以看出,核心執行緒在進行第N+1幀的佈局和記錄繪製操作同時,合成執行緒也在努力進行第N幀的渲染並交給螢幕展示,這裡利用了CPU多核的特性進行併發處理,因此提高了頁面的渲染效率。由此也可知,實際上使用者看到的頁面內容,是上一幀的內容快照,新的一幀還在處理中。

要優化第2個問題,對瀏覽器來說非常困難。只要輸入事件要在核心執行緒執行邏輯,那麼遇到核心執行緒在忙,必然無法立即得到響應。如使用者的大部分輸入事件都跟頁面元素有關係,一旦頁面元素註冊了對應事件的監聽器,監聽器的邏輯程式碼(JavaScript)必須在核心執行緒中執行(V8引擎執行在核心執行緒),因此這種輸入事件經常無法立即得到響應。

由上面的分析知道,使用者的輸入事件無法立即得到響應,是因為需要派發給核心執行緒處理。那有沒有一些輸入事件是可以不經過核心執行緒就能被快速處理的呢?答案是肯定的。

圖片描述

在Chrome中,這類可以不經過核心執行緒就能快速處理的輸入事件為手勢輸入事件(滑動、捏合),手勢輸入事件是由使用者連續的普通輸入事件組合產生,如連續的mousewheel/touchmove事件可能會生成GestureScrollBegin/GestureScrollUpdate等手勢事件。手勢輸入事件可以直接在已經渲染好的內容快照上操作,如滑動手勢事件,直接對頁面已經渲染好的內容快照進行滑動展示即可。由於執行緒化渲染框架的支援,手勢輸入事件可以不經過核心執行緒,直接由合成執行緒在內容快照上直接處理,所以即使此時核心執行緒在忙碌,使用者的手勢輸入事件也可以馬上得到響應。大家可以搞一個簡單的demo驗證一下Chrome瀏覽器的這個特性:如在一個有滾動條的頁面內通過JavaScript執行一段死迴圈的程式碼(while-true之類的),這個時候再去嘗試上下滑動頁面,你會發現此時頁面仍能流暢地滑動。

由此可知,Chrome瀏覽器對於手勢輸入事件的響應是非常快的,因為它可以不需要經過核心執行緒,直接由合成執行緒快速處理。然而手勢輸入事件的產生可能需要核心執行緒,這會導致Chrome對手勢輸入事件的優化效果大打折扣。由前面介紹知道,手勢輸入事件是由連續的普通輸入事件組成,而這些普通的輸入事件可能會被對應的事件監聽器內部呼叫preventDefault函式來阻止掉事件的預設行為,在這種場景下不會產生手勢輸入事件。如連續的mousewheel事件預設可以產生GestureScrollUpdate事件,但是如果監聽器內部呼叫了preventDefault函式,那麼這種情況下則不應該產生GestureScrollUpdate手勢事件的。瀏覽器只有等核心執行緒執行到事件監聽器對應的JavaScript程式碼時,才能知道內部是否會呼叫preventDefault函式來阻止事件的預設行為,所以瀏覽器本身是沒有辦法對這種場景進行優化的。這種場景下,使用者的手勢事件無法快速產生,會導致頁面無法快速執行滑動邏輯,從而讓使用者感覺到頁面卡頓。

圖片描述

而Chrome團隊從統計資料中分析得出,註冊了mousewheel/touch相關事件監聽器的頁面中,80%的頁面內部都不會呼叫preventDefault函式來阻止事件的預設行為。對於這80%的頁面,即使監聽器內部什麼都沒有做,相對沒有註冊mousewheel/touch事件監聽器的頁面,在滑動流暢度上,有10%的頁面增加至少100ms的延遲,1%的頁面甚至增加500ms以上的延遲。Chrome團隊認為對於統計中的這80%的頁面來說,他們都是不希望因為註冊mousewheel/touch相關事件監聽器而導致滑動延遲增加的。點選這裡可以體驗頁面註冊後導致的滑動延遲,如上圖。

如果能讓Web開發者來明確告訴瀏覽器,監聽器內部不會呼叫preventDefault函式來禁止預設的事件行為,那麼瀏覽器將能快速生成手勢輸入事件,從而讓頁面響應更快。

介紹完這裡,大家應該明白Chrome瀏覽器為什麼需要Passive Event Listeners特性了。接下來,我們來看看Passive Event Listeners特性是怎麼實現的。

Passive Event Listeners的實現

function handler(event) {
        console.log(event.type); // log event type
    }
document.addEventListener("mousewheel", handler, {passive:true});

為了更好地理解Passive Event Listeners特性,我們接下來了解一下它的實現過程。如上面程式碼所示,假定頁面中註冊了mousewheel事件的被動監聽器,此時使用者開始滑動滑鼠滾輪來滑動頁面。

圖片描述

如上圖所述,使用者的滑鼠滾輪事件(WM_MouseWheel)由作業系統核心捕捉後,作業系統會將該事件派發給瀏覽器的UI執行緒處理。UI執行緒內部將系統的WM_MouseWheel事件轉換為Chrome的WebInputEvent::MouseWheel事件後,接著通過IPC通道派發給合成執行緒的輸入事件處理器處理。
合成執行緒的輸入事件處理器收到WebInputEvent::MouseWheel事件後,內部先會查詢MouseWheel事件監聽器的型別屬性,然後根據監聽器的型別屬性值來進行不同邏輯的處理。

目前Chrome中監聽器的型別屬性值主要有四種:EventListenerProperties::kNone、EventListenerProperties::kPassive、EventListenerProperties::kBlocking、EventListenerProperties::kBlockingAndPassive,如下程式碼所述:

enum class EventListenerProperties {
  kNone,
  kPassive,
  kBlocking,
  kBlockingAndPassive,
  kMax
};

在Chrome中,kBlocking和kBlockingAndPassive型別屬性的處理邏輯是一樣的,這個不難理解,只要存在一個非passive型別的事件監聽器,那麼都有可能阻止事件的預設行為。接下來,我們瞭解一下不同型別屬性監聽器的實現邏輯。

場景1:EventListenerProperties::kNone型別

當事件監聽器的型別屬性為EventListenerProperties::kNone時,意味著當前頁面內沒有註冊對應事件的監聽器。對於這種場景(如上圖中的MouseWheel Handlers:No分支),合成執行緒會馬上傳送一個MouseWheel的ACK訊息給UI執行緒,UI執行緒收到MouseWheel的ACK訊息後,會判斷該事件是否被消費(Comsumed,即呼叫了preventDefault),如果已經被消費,則什麼都不做。否則,UI執行緒會產生一個滑動手勢事件(如果當前不是在滑動過程,手勢事件為GestureScrollBegin,否則為GestureScrollUpdate),並滑動手勢事件通過IPC通道派發給合成執行緒處理,合成執行緒收到該滑動手勢事件之後,直接對內容快照進行滑動處理,並展示給到螢幕上。這種場景下,由於沒有涉及到核心執行緒處理,使用者的輸入響應會非常及時。

場景2:EventListenerProperties::kBlockingAndPassivecc::EventListenerProperties::kBlocking型別

當事件監聽器的型別屬性為EventListenerProperties::kBlockingAndPassiveEventListenerProperties::kBlocking時,意味著當前頁面至少存在一個非passive型別的事件監聽器。對應這種場景(如上圖中的MouseWheel Handlers:YES-Passive:No分支),合成執行緒無法知道對應的監聽器內部是否會呼叫preventDefault函式來阻止預設行為,此時合成執行緒只能將該輸入事件派發給核心執行緒處理(Dispatch Event to Main Thread)。等核心執行緒執行完監聽器的處理邏輯後(Run JS Handler),再發送一個MouseWheel的ACK訊息給UI執行緒,UI執行緒收到Mouse Wheel的ACK訊息後的處理邏輯跟場景1一致。這種場景下,手勢輸入事件必須等待事件監聽器邏輯處理完成後才會產生並派發給合成執行緒處理,由於事件監聽器邏輯的執行時機不確定,將非常容易導致使用者的輸入事件無法立即響應。

場景3:EventListenerProperties::kPassive型別

當事件監聽器的型別屬性為EventListenerProperties::kPassive時,意味著當前頁面只存在passive型別的事件監聽器。對於這種場景(如上圖中的MouseWheel Handlers:YES-Passive:YES分支),合成執行緒首先會發送一個MouseWheel的ACK訊息給UI執行緒,執行跟場景1中一樣的邏輯,同時將該事件派發給核心執行緒處理,執行跟場景2相似的邏輯,但是在Run JS Handlers完成後,不會再發送Mouse Wheel事件的ACK訊息。這種場景下,實際上是場景1和場景2的組合,兩個場景是並行處理的,因此使用者的MouseWheel輸入事件能被立刻響應,也不會受到核心執行緒的事件監聽器處理邏輯影響。

對於場景1和場景3的滑動,在Chrome中稱為fast scroll模式,而場景2則稱為slow scroll模式。

總結

經過上面的分析,我們瞭解到了Passive Event Listeners特性實際上是為了解決瀏覽器頁面滑動流暢度而設計的,它通過擴充套件事件屬性passive讓Web開發者來告知瀏覽器監聽器是否會阻止事件的預設行為,從而讓瀏覽器可以更智慧地決策並優化,這其中涉及到了Chrome的多執行緒渲染框架、輸入事件處理等知識。

編輯推薦:架構技術實踐系列文章(部分):

2016年11月18日-20日,由CSDN重磅打造的年終技術盛會SDCC 2016中國軟體開發者大會將在北京舉行,大會秉承乾貨實料(案例)的內容原則,本次大會共設定了12大專題、近百位的演講嘉賓,並邀請業內頂尖的CTO、架構師和技術專家,與參會嘉賓共同探討電商架構、高可用架構、程式語言、架構師進階、微信開發、前端、平臺架構演進、基於Spark的大資料系統設計、自動化運維與容器實踐、高吞吐資料庫系統設計要領、移動視訊直播技術等。10月14日仍是五折搶票,最低1400元,註冊參會

相關推薦

頁面滑動流暢特性Passive Event Listeners

宣告:本文來自騰訊增值產品部官方公眾號小時光茶社,為CSDN原創投稿,未經許可,禁止任何形式的轉載。 作者:陳志興,騰訊高階工程師,11年畢業後加入騰訊,期間主要負責過QQ檔案傳輸質量優化、本地檔案共享跨網訪問、多終端訊息同步、手Q個性化業務開發、手Q W

頁面滑動流暢起來的特性Passive Event Listeners

function handler(event) { console.log(event.type); // log event type } document.addEventListener("mousewheel", handler, {passive:true});

二、docker 映象容器常用操作(我們用docker 溜)

前言 上篇講了我們如何安裝docker,現在該我們一展拳腳的時候了。接下來讓我們一起學習一下docker常見的操作,讓我們能夠會使用 docker。 基本概念 在講使用之前,還是先將一下docker 的基本概念,畢竟上篇就講了docker 的安裝。一些基本的名詞還是需要了解一下的。 docker 最重要的就是

現在異常用的溜

主動 編譯 tun 等於 turn 導致 ret 我們 必須 return 使線程跳出函數,之後需要進行接收throw RuntimeException 使線程跳出函數, 之後需要進行接收區別在於跳出函數的範圍,return的範圍是固定的,往外跳一層throw Runtim

好系統U盤啟動教你一招win10系統快的

程序 硬盤 下載 ews ftp nag images ESS win10系統 電腦系統用的時間長了,會發現越來越慢,最大的一方面原因是因為下載的軟件及產生的緩存垃圾,當然了,也會有電腦自身的程序和硬件性能的原因。如何讓win10的電腦操作系統快起來?下面來給大家說三種方法

java9特性在接口中用pirvate方法default(java8接口特性)更簡練

譯文 OS 實現 -- 直接 幫助 創建 str 新特性 Java8 帶來了許多改變,其中之一就是default修飾的接口方法。 這些方法改變了我們已知的接口,現在我們能夠在接口中定義默認實現方法。默認實現方法的不同之處在於,在接口中用default修飾抽象方法後,該方法可

黑馬程式設計師雲道頁面——鞏固html和css(特性沒有使用)

注意:        一.寫的是黑馬程式設計師跟著老師寫的,有點小差別,主要看註釋理解結構        二.以前學thml和css沒有深入理解到盒子的坍塌問題,注意理解外邊距的合併。有兩種情況,一個是兩個盒子緊

ASP.NET MVC5 特性Attribute路由使用詳解

ref 否則 back default static 引入 擁有 bsp pathinfo 1、什麽是Attribute路由?怎麽樣啟用Attribute路由?   微軟在 ASP.NET MVC5 中引入了一種新型路由:Attribute路由,顧名思義,Attribute

H5特性

cat sage drag 資源共享 document doc arch query 資源 新增選擇器 document.querySelector、document.querySelectorAll 拖拽釋放(Drag and drop) API 媒體播放的 vi

H5特性video與audio的使用

一個 ace mil lin pro 地址 mp4 epg 屬性方法 HTML5 DOM 為 <audio> 和 <video> 元素提供了方法、屬性和事件。 這些方法、屬性和事件允許您使用 JavaScript 來操作 &l

Oracle 12.1特性在線rename或relocate數據文件

oracle 12 move datafile在Oracle12.1之前的版本中要重命名數據文件或移動數據文件需要關閉數據庫或把表空間/數據文件置為offline狀態才可以,參考之前總結的Oracle修改數據文件名/移動數據文件。但到了12.1版本,可以直接在數據文件online狀態下把數據文件重命名或移動數

ES6特性解構賦值(上)

變量賦值 什麽 模式 註意 只需要 一個數 html 代碼 內容 1:什麽是解構賦值 按照一貫的套路,接下來的內容是解釋:什麽是解構賦值? 來看看官方的解釋: ES6允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱為解構(

C# 7.0 特性本地方法

性能 erro 區別 visual html 修飾 之間 style ria C# 7.0:本地方法 VS 2017 的 C# 7.0 中引入了本地方法,本地方法是一種語法糖,允許我們在方法內定義本地方法。更加類似於函數式語言,但是,本質上還是基於面向對象實現的。 1.

SQL Server 2016特性Temporal Table

clu let tween 事務 之間 small 激活 設置 版本 什麽是系統版本的Temporal Table 系統版本的Temporal Table是可以保存歷史修改數據並且可以簡單的指定時間分析的用戶表。 這個Temporal Table就是系統版本的Tempora

Java8特性接口的默認方法與接口的靜態方法

err 類比 urn 多態 class 數組保存 統一 body bstr 接口的定義 接口的作用是定義該類型的實例要具有的功能,也就是必須執行哪些工作,並且不需要關心這些工作是怎麽具體進行的。接口定義的方法沒有方法體,並且接口不允許定義實例變量。如果一個類實現了這個接口就

Zabbix 3.4.6 特性歷史數據支持 Elasticsearch

openssl security sql rod start postgre pre first sim 一、升級 cURL註:先升級 cURL,然後在安裝 Zabbix Server,否則報如下錯誤:cannot initialize history storage: c

SQL Server 2016特性DROP IF EXISTS

obj 宋體 fff article 特性 這一 ble family base 原文:SQL Server 2016新特性:DROP IF EXISTS ?? 在我們寫T-SQL要刪除某個對象(表、存儲過程等)時,一

JDK8特性接口的靜態方法和默認方法

tool IT highlight RR 類名 another 存在 ltm tar 在jdk8之前,interface之中可以定義變量和方法,變量必須是public、static、final的,方法必須是public、abstract的。由於這些修飾符都是默認的,所以在J

版本12.1特性優先級負載均衡法

NetScaler如果不希望負載均衡,一組服務器down掉才啟用下一組怎麽辦?在以前版本的實現方法是backup vserver,或者做個反向的monitor。如果實現的組比較多,邏輯上還是有些復雜的。 而在 12.1單獨實現了最簡單的邏輯:優配先級 新版多了一個按鈕 優先級負載均衡 建立vserver時定義

MySQL8.0 特性Partial Update of LOB Column

sql摘要: MySQL8.0對json進行了比較完善的支持, 我們知道json具有比較特殊的存儲格式,通常存在多個key value鍵值對,對於類似更新操作通常不會更新整個json列,而是某些鍵值。 對於某些復雜的應用,json列的數據可能會變的非常龐大,這時候一個突出的問題是:innodb並不識別json