頁面渲染:效能分析

陳贇發表於2018-08-30

CSS屬性的渲染影響列表>>


performance介紹


Chrome DevTools的performance皮膚可以記錄和分析頁面在執行時的所有活動。

配合無痕模式,可以避免chrome外掛的影響。

幀率圖

1. 錄製檢視

頁面渲染:效能分析
上面部分是幀數資訊:

  • 1312.3ms:一幀的時間
  • 1fps: 每秒的幀數

下面部分是網頁快照,瀏覽器按照一定時間間隔擷取。

2. 實時檢視

快捷鍵ctrl + shift + p可以實時檢視幀率

頁面渲染:效能分析
還可以進行其它配置

頁面渲染:效能分析

  • Paint Flashing 高亮顯示網頁中需要被重繪的部分。
  • Layer Borders 顯示Layer邊界。
  • FPS Meter 每一秒的幀細節,幀速率的分佈資訊和GPU的記憶體使用情況。
  • Scrolling Performance Issues 分析滑鼠滾動時的效能問題,會顯示使螢幕滾動變慢的區域。
  • Emulate CSS Media 模擬CSS媒體型別,檢視不同的裝置上CSS樣式效果,可能的媒體型別選項有print、screen

火焰圖

頁面渲染:效能分析

  • Loading:網路通訊和HTML解析
  • Scripting:Javascript執行
  • Rendering:樣式計算和佈局,即重排
  • Painting:重繪 對應的詳細事件 >>>

怎麼看出效能問題?

1. 紅色小三角

頁面渲染:效能分析
常見的原因為:

  • handler took xxx ms 操作消耗太多時間
  • forced reflow is likely performance bottleneck “強制同步佈局”可能會導致效能問題,通常是因為修改樣式後讀取屬性,導致了瀏覽器必須重新渲染以獲取最新的屬性值。

2. 佈局抖動

頁面渲染:效能分析
“佈局抖動”是指反覆出現“強制同步佈局”情況。 這種情況會在 JavaScript 從 DOM反覆地寫入和讀取時出現,將會強制瀏覽器反覆重新計算佈局。佈局抖動也會導致長幀,使頁面卡頓。

3. 長幀

頁面渲染:效能分析
長幀表示一幀的時間過長,會影響頁面的載入速度與動畫的流暢性,這時會感受到頁面載入慢或動畫卡頓。

動畫播放時每秒的幀數最好能夠達到60幀,也就是每幀16.6ms。


例項


1. 解析html(不包含js css外部檔案)

頁面渲染:效能分析

  • readystatechange(第一個)(文件已載入和解析) 此時狀態為interactive,表示文件已載入和解析但資源仍在載入,該狀態通常緊接著會觸發DomContentLoaded。
  • DOMContentLoaded (DOM樹構建完成) html文件被載入和解析成功,DOM樹構建完成時會觸發。
  • Recalculate Style(CSSOM構建完成) 通過新增和刪除元素,更改屬性、類或通過動畫來更改 DOM,全都會導致瀏覽器重新計算元素樣式,在很多情況下還會對頁面或頁面的一部分進行佈局(即自動重排)。重新計算樣式的步驟可以分為兩步:
  1. 瀏覽器計算出給指定元素應用哪些類、偽選擇器和 ID。
  2. 從匹配選擇器中獲取所有樣式規則,並計算出此元素的最終樣式。
  • readystatechange(第二個)(文件已載入和解析,且資源也載入完成) 此時狀態為complete,表示文件和資源都已載入完成,該狀態通常緊接著會觸發load。
  • load事件 文件和資源都已載入完成時會觸發。更多頁面載入完成事件>>
  • Layout 佈局幾乎總是作用在整個文件,但還是主要看影響的節點個數。

頁面渲染:效能分析

2. 解析html(包含js css外部檔案)

頁面渲染:效能分析

  • Evaluate Script (執行js)
  • layout變為不在ParseHtml中執行 可能是因為CSS檔案或JS檔案的載入阻塞了整個頁面的渲染過程,因為js和css都可能對標籤進行樣式的設定。如果不存在檔案,就不會存在等待載入的問題。

3. 改變背景色(重繪)

頁面渲染:效能分析

4. 改變高度(重排)

頁面渲染:效能分析

相對於重繪多了個Layout

5. 圖片資源載入(img或bg)

如果圖片標籤尺寸不變,則會觸發一次重繪

頁面渲染:效能分析


遵循的原則


1. 關於阻塞

  • css載入 不會阻塞 DOM樹的解析;CSS解析 會阻塞;
  • css載入和解析 會阻塞 js(所以內聯樣式不用載入效能較高,適用於第一屏)
  • js 會阻塞 DOM樹的解析 (因為js會改變DOM樹內容)
  • css引入的字型檔案載入 也會阻塞 js , 頁面渲染

2. 關於與頁面渲染過程的對應

  1. js執行時:這時應該只是構建了前面部分的dom樹和CSSOM樹,因為js需要通過dom api和CSSOM api操作前面部分的標籤的內容和樣式。
  2. DOM樹構建完成:DomContentLoaded事件
  3. CSSOM構建完成、Render Tree構建完成:Recalculate Style
  4. Layout:Layout事件
  5. paint:Paint(圖片層繪製) 和 Composite Layers(圖片層合併),除了transform 或 opacity屬性之外,更改任何屬性始終都會觸發繪製Paint
  6. reflow重排:3 4 5步走一遍
  7. repaint重繪:3 5步走一遍
  8. 更改一個既不要佈局也不要繪製的屬性:3步 + Composite Layers,此行為在678重新渲染步驟中開銷最小,適合動畫或滾動,具體比如transfrom opacity。

3. 關於chrom瀏覽器的一些行為

  • 渲染佇列:瀏覽器存在一個渲染佇列,用於將多次連續的重排和重繪操作變成一次。當你進行DOM的讀操作時,如果佇列不為空,chrome會清空佇列,立即進行重排或重繪;如果為空,chrome不會做出多餘的操作。
  • 佈局:佈局或重排中瀏覽器需要計算元素要佔據的空間大小及其在螢幕的位置,網頁的佈局模式意味著一個元素可能影響其他元素,例如 <body> 元素的寬度一般會影響其子元素的寬度以及樹中各處的節點。
  • 繪製與合成:繪製一般是在多個表面(通常稱為層)上完成的,因此瀏覽器需要將它們按正確順序繪製到螢幕上,以便正確渲染頁面。
  • css選擇器:對於複雜的css選擇器,瀏覽器需要花更多時間來確定元素的樣式,因此以類為中心的css編寫原則,老外比較推崇,比如:nth-last-child偽類可以用獨立的類替代(不然它怎麼叫偽類( ̄▽ ̄)")。

效能優化


阻塞優化

  • js存在問題: 1)js載入和執行都會阻塞DOM解析和頁面渲染 2)如果引用第三方指令碼,當第三方服務商請求延遲時,頁面會白屏; js解決辦法: 頁面可以通過新增關鍵字defer和async來非同步載入js。

頁面渲染:效能分析

  1. 兩者都是非同步載入,不同的是: async(參考ajax):非同步載入時不會阻塞DOM解析和頁面渲染;執行時間為載入完成時;執行時會阻塞,但這時可能DOM已經解析完成,甚至頁面已經渲染;另外會影響js檔案執行順序。 defer:非同步載入時不會阻塞DOM解析和頁面渲染;執行時間為DOM解析完成之後、DOMContentLoaded事件之前(所以會阻塞DCLoad事件和jquery的ready事件);執行時DOM已經解析完成,只會阻塞頁面渲染。
  2. js檔案載入順序 同步 > 非同步 同是async 按載入完成順序 同是defer 按引入順序 async defer 正常不一起用

減少重新渲染

關於CSS

  • 使用簡單的樣式表。樣式表越簡單,重排和重繪就越快。具體為:
  1. 減低選擇器的複雜性,少用偽類;使用以類為中心的方法,例如BEM;
  2. 減少必須計算其樣式的元素的數量,應當儘可能減少宣告為無效的元素的數量。
  • 減少DOM元素層級。重排和重繪的DOM元素層級越高,成本就越高。
  • 多利用display:none。display:none的元素沒有在渲染樹,因而也不會進行重排和重繪
  • 使用CSS動畫而不是JS動畫。CSS動畫優於JS動畫,是由於CSS改變的是translate的值,不會引起offsetLeft、offsetTop等位置值的改變。
  • 使用absolute而不是float。position屬性為absolute或fixed的元素在重排的開銷比float少,因為不用考慮它對其他元素的影響。
  • 使用div而不是table。因為一個很小的改動可能都會引起整個table的重新佈局,比如說td內容改變。

關於JS

  • DOM的多個讀操作(或多個寫操作)應該放在一起,不要穿插進行。因為連續地設定元素樣式(寫操作),瀏覽器會一次性執行,即只觸發一次重排或重繪,但如果在幾個寫操作間插入讀取樣式的操作,瀏覽器則不得不立即重排或重繪。
  • 一次性改變樣式。不要一條條地改變樣式,而要通過改變class,或者el.style.csstext屬性。
  • 使用離線DOM來改變元素樣式。比如cloneNode()克隆節點,然後再替換掉元素節點 或者 display:none → 改動 → 顯示
  • 儘量修改層級較低的DOM。
  • 不要在迴圈中重複讀取DOM節點屬性值。
  • 使用 window.requestAnimationFrame()、window.requestIdleCallback() 這兩個方法控制重新渲染。

提高fps(frame per second)

網頁動畫的每一幀(frame)都是一次重新渲染,將一幀送到螢幕會採用如下順序:

頁面渲染:效能分析
幀數與動畫流暢度的關係如下:

  • < 24幀 :人眼能感受到停頓
  • 30 - 60 幀數:比較流暢
  • 70 - 80:德芙,縱享新絲滑 考慮到多數顯示器的重新整理頻率為60Hz,即每秒重新整理60次,如果fps高於60,每秒將輸出高於60張的畫面,那麼假如fps > 重新整理頻率,多輸出的畫面將是無效的幀數,流暢度沒有任何提升,但也沒啥壞處(手動滑稽d:)。

這裡想說地是fps最好能達到瀏覽器重新整理頻率,因而在播放動畫的時候,注意不要執行太多耗時耗效能的操作。

手動控制重新渲染

window.requestAnimationFrame() 方法可以將某些程式碼統一放到下一次重新渲染時執行。具體是將js程式碼放在下一幀開始時執行。如果使用setTimeout 或 setInterval 來執行動畫之類的視覺變化,其回撥可能在幀的某個時間點執行,可能在末尾,這會使我們丟失幀,導致卡頓。

  1. 處理“佈局抖動” 反覆讀寫屬性會導致佈局抖動,導致長幀。
function doubleHeight(element) {
    var currentHeight = element.clientWidth;
    element.style.width = (currentHeight / 2) + 'px';
    element.style.height = '80px';
}
var elements = document.getElementsByTagName('tr');
for (var i = 0; i < elements.length; i++) {
    doubleHeight(elements[i]);
}
複製程式碼

頁面渲染:效能分析
將doubleHeight函式改成下面這樣:

function doubleHeight(element) {
    var currentHeight = element.clientHeight;
    window.requestAnimationFrame(function () {
      element.style.height = (currentHeight * 2) + 'px';
    });
}
複製程式碼

頁面渲染:效能分析

2)頁面滾動事件(scroll)

$(window).on('scroll', function() {
   window.requestAnimationFrame(scrollHandler);
});
複製程式碼

3)最適合用於動畫

結合專案

現在專案中,頁面(以“任務”頁面為例)在載入時都會請求一些ajax資料,比如datagrid,tree資料等等,還有些ajax資料只是預載入。如果這些ajax在頁面渲染前完成請求,則會阻塞頁面渲染。所以同一個頁面不同網速下會有兩種渲染順序:

  • 在渲染之後執行

頁面渲染:效能分析

  • 在渲染之前執行

頁面渲染:效能分析

解決辦法

  1. 在所有資源載入完後進行ajax請求。將datagrid等控制元件的資料載入放在$(window).load()事件中。
  2. 延遲初始化modal中的內容

效果

頁面渲染:效能分析

頁面渲染:效能分析


問題


問:請求js檔案時,請求和執行順序是什麼?

答:請求會一起發出;執行順序按引入的順序,不會因為後一個先返回資料而先執行。

問:頁面載入時提前ajax請求一些資料,會不會影響效能?

答:可能會,可能不會。ajax請求時不影響效能,請求後執行回撥函式會影響。ajax回撥函式會在請求完成且js主程式執行完後執行,等到我們能看到頁面,需要經歷頁面解析和渲染這兩個過程,如果頁面渲染前ajax回撥執行了,那將阻塞渲染過程。

問:為什麼jquery通常在ready方法中執行程式碼?

答:ready 監測的是 DOMContentLoaded事件,也就是監測DOM樹構建完成。

附錄


loading 事件

事件描述
Parse HTML 瀏覽器執行HTML檔案解析
Parse Stylesheet 瀏覽器執行CSS檔案解析(單指外部CSS檔案)
Finish Loading 網路請求完畢事件
Receive Data 請求的響應資料到達事件,如果響應資料很大(拆包),可能會多次觸發該事件
Receive Response 響應頭報文到達時觸發
Send Request 傳送網路請求時觸發

Scripting事件

事件描述
Animation Frame Fired 一個定義好的動畫幀發生並開始回撥處理時觸發
Cancel Animation Frame 取消一個動畫幀時觸發
GC Event 垃圾回收時觸發
DOMContentLoaded 當頁面中的DOM內容載入並解析完畢時觸發
Evaluate Script A script was evaluated.
Event js事件
Function Call 只有當瀏覽器進入到js引擎中時觸發
Install Timer 建立計時器(呼叫setTimeout()和setInterval())時觸發
Request Animation Frame A requestAnimationFrame() call scheduled a new frame
Remove Timer 當清除一個計時器時觸發
Time 呼叫console.time()觸發
Time End 呼叫console.timeEnd()觸發
Timer Fired 定時器啟用回撥後觸發
XHR Ready State Change 當一個非同步請求為就緒狀態後觸發
XHR Load 當一個非同步請求完成載入後觸發

Rendering事件

事件描述
Invalidate layout 當DOM更改導致頁面佈局失效時觸發
Layout 頁面佈局計算執行時觸發
Recalculate style Chrome重新計算元素樣式時觸發
Scroll 內嵌的視窗滾動時觸發

Painting事件

事件描述
Composite Layers Chrome的渲染引擎完成圖片層合併時觸發
Image Decode 一個圖片資源完成解碼後觸發
Image Resize 一個圖片被修改尺寸後觸發
Paint 合併後的層被繪製到對應顯示區域後觸發

相關文章