本文會介紹瀏覽器中幀(Frame)的概念,它的流程是怎麼樣的。前端
至於寫這個文章的出發點在於,我好奇瀏覽器中像素工做流程是怎麼樣的,何時開始的,最後的結果是什麼。git
基於這些好奇,查閱了些外文資料,本文提供了些參考,參考連接在文末。github
最近搞了性能優化思惟導圖,還在持續輸出中,點擊這裏web
在講幀的概念前,咱們得從背景開始看起,也就是渲染頁面的這個過程,有哪些關鍵性的路徑呢。chrome
像素輸出到頁面,確定經歷了不少的過程,那咱們做爲前端工程師,工做中須要注意的點是哪些呢,這裏給出參考:canvas
這五個主要的部分,應該是咱們值得去關注的,由於咱們擁有最大控制權的部分。至於每個過程具體是怎麼樣的呢,不清楚的能夠參考下圖:瀏覽器
因此在這麼一個像素的管道里,每部分都有可能形成卡頓,因此咱們須要額外的關注這些,畢竟那一部分不當,都會開了沒必要要的性能開銷。性能優化
當時個人疑問是: 難道每一幀都老是會通過管道每一個部分的處理嘛,其實不是這樣子的,從視覺的角度來講,管道針對指定幀的運行一般有三種方式:markdown
若是咱們以第三種方式來更新視圖,也就是更改一個既不要佈局也不要繪製的屬性,則瀏覽器將跳到只執行合成。前端工程師
爲了更加具體的驗證上述的過程,能夠動手跑一個demo,來驗證一下。
咱們添加多個dom元素進行動畫,效果更佳明顯,接着咱們打開Performance,Record這個過程,咱們須要關注的是Main選項卡,也就是主線程,咱們在放大里面的Task,就有了下圖:
經歷的過程,也是很清楚看到,Update Layer Tree -->> Layout -->> Paint -->> Composite Layers。
若是你不是很清楚Performance中名稱的含義,能夠參考下面這篇文章,點這裏:
接着,咱們按下,Optimize按鈕,按照以前的流程走,Record後,發現不對勁,仍是這樣子步驟,難道是哪裏存在問題嘛,好奇的我,打開了Sources面板,而後就發現了:
它的源碼優化動畫,使用的是rAF,瞭解過的人必定不會陌生,你能夠簡單的理解就是:按幀對網頁進行重繪。這裏就引出了幀的概念,後續會說明。
rAF的詳細介紹,後續會對它進行梳理,能夠持續關注。
回到前面咱們設想的點,咱們如何才能保證直接跳到合成過程,避免Layout以及Paint呢,固然有,咱們須要對app.js中的uppdate函數進行改造,使用transform: translateX(0px); 作動畫,作完update函數的處理邏輯後,咱們再次Record一下:
從Task子任務中,咱們能夠發現,Layout -->> Paint, 佈局和繪製的過程跳過了。這也是爲何咱們常說的須要避免迴流與重繪。從主線程上來看,可以徹底的避免這些過程,避免了不少的運算開銷。
這也是爲何常常能夠看到這樣子的建議:
will-change
或 translateZ
提高移動的元素。至於使用will-change和translatez來提高圖層,這又是另外知識點了,這裏就不張開了。
介紹到這裏,咱們已經清楚的明白,避免迴流和重繪的意義,那麼咱們提到的幀和rAF 與 渲染路徑有啥關係呢。
我作的第一件事情就是google,而後維基百科給出以下定義:
嗯,不是很好理解,知道我找到了這張圖,才解答了個人困惑:
這就真的是一圖勝千言。
這個圖,你能夠理解成就是像素放到屏幕的完整過程。你確定對裏面的一些關鍵信息很迷惑,這裏做出一些解釋。
接下來大部份內容都是翻譯的,沒有更多的總結,感興趣能夠看看原文。
映入眼簾的就是進程:
如今咱們來看看Renderer Process中的線程。
在不少方面,你應該把Compositor線程視爲 "大老闆"。雖然它不運行JavaScript、Layout、Paint或其餘任何東西,但它是徹底負責啓動主線程工做的線程,而後將幀運送到屏幕上。若是它不須要等待輸入事件處理程序,它就能夠在等待主線程完成工做的同時運送幀。
你也能夠想象Service Workers和Web Workers生活在這個過程當中,不過我沒有把他們包括在內,由於這讓事情變得更加複雜。
讓咱們成主線程開始吧。
讓咱們一步步走過這個流程,從vsync到像素,並談談在事件的 "全胖 "版本中事情是如何進行的。值得記住的是,瀏覽器不須要執行全部這些步驟,這取決於什麼是必要的。例如,若是沒有新的HTML要解析,那麼解析HTML就不會啓動。事實上,不少時候,提升性能的最好方法就是簡單地消除部分流程被啓動的必要性!
一樣值得注意的是,樣式和佈局下的紅色箭頭彷佛指向了 requestAnimationFrame。在你的代碼中徹底有可能意外地觸發這二者。這就是所謂的強制同步佈局(或樣式,視狀況而定),它一般對性能不利。
Frame Start(開始新的一幀):
垂直同步信號觸發,開始渲染新的一幀圖像。
Input event handlers (輸入事件的處理)。
-輸入數據從合成器線程傳遞給主線程上的任何輸入事件處理程序。
全部的輸入事件處理程序(觸摸移動、滾動、點擊)都應該首先啓動,每幀一次,但狀況不必定如此。
調度器會作出最大努力的嘗試,其成功率在不一樣的操做系統中有所不一樣。在用戶交互和事件進入主線程處理之間也有一些延遲。
requestAnimationFrame
:
這是對屏幕進行視覺更新的理想位置,由於你有新鮮的輸入數據,並且這是你要獲得的最接近vsync的地方。
其餘的視覺任務,好比樣式計算,都是在這個任務以後進行的,因此它的理想位置是突變元素。
若是你突變--好比說--100個類,這不會致使100個樣式計算;它們將被分批處理,並在後面處理。惟一須要注意的是,你不要查詢任何計算過的樣式或佈局屬性(好比el.style.backgroundImage或el.style.offsetWidth)。
若是你這樣作,你會把從新計算的樣式、佈局或二者都向前帶,致使強制的同步佈局,或者更糟糕的是,佈局打亂。
Parse HTML (解析 HTML):
任何新添加的HTML都會被處理,並建立DOM元素。
在頁面加載過程當中或appendChild等操做後,你可能會看到更多的這種狀況。
Recalc Styles(從新計算樣式):
樣式是爲任何新添加或突變的東西計算的,這多是整個樹,也多是範圍,這取決於改變了什麼。
這多是整個樹,也多是範圍縮小,這取決於改變了什麼。
例如,改變主體上的類可能影響深遠,但值得注意的是,瀏覽器已經很是聰明地自動限制了樣式計算的範圍。
Layout(繪製):
計算每一個可見元素的幾何信息(每一個元素的位置和大小)。它一般對整個文檔進行計算,一般使計算成本與DOM大小成正比。
Update Layer Tree(更新圖層樹):
Paint:
Composite(合成):
計算出圖層和瓷磚的信息,並傳回給合成器線程來處理。
這將會考慮到,除其餘事項外,像will-change,重疊元素,以及任何硬件加速的canvases。
Raster Scheduled (柵格化規劃)and Rasterize(柵格化):
如今會執行在Paint任務中記錄的繪製調用。這是在Compositor Tile Workers中完成的,其數量取決於平臺和設備能力。
例如,在Android上,你一般會發現一個Worker,在桌面上,你有時能夠找到四個。柵格化是以圖層爲單位進行的,每一個圖層都是由瓷磚組成的。
Frame End(幀結束):
當各個圖層的磁貼都柵格化後,任何新的磁貼都會和輸入數據(可能在事件處理程序中被改變)一塊兒提交給GPU線程。
Frame Ships(發送幀):
大體上,整個的過程就是上述。
要說這個的話,咱們得拿requestAnimationFrame來類比,requestAnimationFrame是在從新渲染屏幕以前執行的,上面提到的rAF,當時作的就是優化動畫,因此很適合作動畫。
requestIdleCallback你經過主線程裏面中的Task去查找的話,會發現它是在渲染屏幕以後執行,經過查閱文章發現,通常會看瀏覽器是否空閒。
這裏篇幅有限,想要了解這個的話,推薦一篇文章:
最近查閱外文文獻,發現要學的東西太多了,若是這篇文章有寫的不對,或者翻譯不佳的地方,歡迎小夥伴指出。
原文首發地址點這裏,歡迎你們關注公衆號「TianTianUp」。
我是TianTian,咱們下一期見!!!
[1] w3c-longTasks: github.com/w3c/longtas…
[2] chrome-fps-meter: developer.chrome.com/docs/devtoo…
[3] devtools-samples: googlechrome.github.io/devtools-sa…
[4] Analyze runtime performance: developer.chrome.com/docs/devtoo…
[5] Timeline Event Reference: developer.chrome.com/docs/devtoo…
[6] The Anatomy of a Frame: aerotwist.com/blog/the-an…
[7] performance-rendering: developers.google.com/web/fundame…
[8] 維基百科: zh.wikipedia.org/wiki/