前言
最近看了Google的Learn How to Develop the Next Generation of Applications for the Web和Udacity上介紹關於Performance的影片在此紀錄一下,範例及圖片大多取自於其中。
DOM & CSSOM
HTML轉換成DOM (Document Object Model) 的流程如下圖
首先會先將HTML字串
轉換成一個一個的token
再將這些token轉換成一個一個的node
最後就形成了DOM
CSS轉換成CSSOM (CSS Object Model) 的流程如下圖
直接看到轉換成node的部分
p是body的子節點,因為所有可見的內容都是body的一部份。
其中子節點會繼承父節點的屬性。
CSS Selector
|
|
以下兩種選擇會是哪一種比較快?
第一種會找所有的h1並且設置屬性,第二種則是找到所有的p且往上找是否父節點為div。
因此直接選擇h1會比較快。
CSS是從右到左,因為從右邊讀取效率會比較高,從左到右會浪費許多時間在錯誤的查找上。
Why do browsers match CSS selectors from right to left?
渲染樹
建立渲染樹的過程中,會從DOM中找node且在CSSOM找是否有相對應的node然後加到渲染樹上。
由於渲染樹只會加上可見的元素,因此display:none
的span不會加到樹上,但Pseudo Elements則會。
Attribute
viewport
可以告知瀏覽器Layout的寬度應該多長,若沒告知則為預設的980px。
media
若是不希望阻塞渲染,可在css來源的tag加上media屬性,如此一來檔案仍會下載但不會阻塞。
async
可在不需要依賴DOM的script上加上async屬性,如此一來就不會阻塞渲染。
Critical Rendering Path Diagrams
發出HTML請求->發出CSS請求並同時建立DOM->建立CSSOM->渲染
發出HTML請求->發出CSS、JavaScript請求並同時建立DOM(遇到JavaScript處則暫停建立DOM)->建立CSSOM->執行JavaScript->繼續建立DOM->渲染
瀏覽器會使用preload scanner
將所有css和js一起載入。
Pixel Pipeline
利用js修改DOM渲染的流程可以分為以下三種
JS / CSS > Style > Layout > Paint > Composite
像是修改margin-left
、width
等等。JS / CSS > Style > Paint > Composite
像是修改background-image
、color
等等。JS / CSS > Style > Composite
像是修改transform
等等。
需要的流程越少成本就越小,因此要盡量選擇第三種方法。
CSS Triggers可以查看每個style所會觸發的流程。
Load, Idle, Animate, Response
我們必須把真正需要的在Load載入來減少時間,像是基本架構、重要的文字等等。
在Idle的時候則可以載入等一下可能會使用到的東西。
Chrome DevTools
最近Chrome的Timeline和Profiles等等似乎都合併到Performance裡了。
FPS Metor用來查看即時的狀況
Paint Flashing用來查看即時Paint的情形
經由左上角的錄製鈕來逐Frame檢視
Screenshots用來查看每個Frame的當前畫面直接鎖定需要的地方
若是大量動畫的狀態要盡量保持在60fps也就是16ms以下(1000/60)才會順暢。
Summary圖表顯示了所花的時間
Event Log則可以看細部所花的時間,Self Time代表在程式內部的,Total Time則是包含了內部呼叫的其他函式。
開啟Memory選項則可以看到內存的使用情形。
實機測試 + Chrome DevTools
若希望使用開發者工具來檢視手機上的結果
可開啟設定->開發者選項->USB偵錯
在Chrome上連結設chrome://inspect
即可看見手機上的網頁,在需要測試的按inspect
即可進入開發者模式。
若想要直接在手機上開啟本機伺服器檔案,則可以Port forwarding設置且按下Enable port forwarding
即可。
Micro Optimizations
由於JavaScript不見得是照我們寫的運行,我們無法知道如何引擎是做最佳化,所以不需要花時間在微最佳化上。
|
|
requestAnimationFrame
雖然60fps換算下來是16ms,但我們實際上必須要在更短的時間內執行完程式,因為會有額外的時間拿來做style計算、layer管理等等。
setTimeout和setInterval不適合拿來處理動畫,因為若我們在像是style計算中突然要執行JavaScript,如此一來整個渲染流程又會重新執行而造成頻率不一致,而requestAnimationFrame可以妥善的安排JavaScript執行的時間。
|
|
Web Workers
Web Workers可以讓js運行在不同的thread而不會造成阻塞。
Memory Management
- 盡量別用delete,因為JavaScript引擎會自動最佳化,若是delete其中的元素則得重新計算。
- null不會真的清空物件,只會讓物件指向null
- 全域變數不會被GC回收
- 取消綁定事件若不再需要
- 若使用資料快取要妥善管理
考慮以下的情況
即使設為null,setTimeout仍會持續進行,這是因為myRef在closure中指向myObj,因此myObj不會被GC回收。
How To Write Fast, Memory-Efficient JavaScript
Selector
當Selector條件越多效能就越受影響,使用.box-three
會比:nth-child(3)
的選擇好。
Demo
CSS
JavaScript
如此一來每個.box
都會看且判斷是否是偶數個,再逐一往上(左)找是否符合
效能
改進方法如下
CSS
JavaScript
如此一來會直接先找到.gray
才找.box
,省去了一半的數量
效能
Forced Synchronous Layouts
若我們每次都要取得Layout再重新計算,這樣會造成Forced Synchronous Layouts
。
Demo
|
|
可以看到先把Node的寬度快取住會比每次都要重新取得快的許多。
Demo
pizza-perf
改為
查看determineDx函式
發現原本是計算差值然後加上去,這樣子可以改寫成直接給予新的值即可,因此改寫為
CSS or JavaScript?
動畫究竟該用CSS還是JavaScript?
- 當元素是單一的用CSS
- 當需要信號控制像是stop、slow down、reverse等等則用JavaScript
- 更改Layout(位置)或需要Paint是相當耗效能的
- 盡量用transforms或opacity
- 原則上CSS動畫會在
compositor thread
上執行,所以當主執行緒有繁重的任務時動畫不會被干擾,JavaScript則會佔用主執行緒 - 不論CSS或JavaScript動畫只要觸發Layout或Paint都會在主執行緒上執行
CSS Versus JavaScript Animations
Animations and Performance
will-change
利用多個Layer可以讓元素跳過Layout和Paint而直接到Composite,但過多的Layer會造成管理Layer上效能的問題。
will-change
屬性會告知瀏覽器即將運行的事件,讓瀏覽器建立一個Layer
也可以在will-change上設置top、left等等,雖然會增加一層Layer但仍得進行Layout和Paint,因此不會有太大的改善。
在舊的瀏覽器上可以使用translateZ
達到同樣的效果,一樣會告知瀏覽器建立Layer。
可以用Chrome的DevTools的Layer標籤查看網頁Layer情形。
可在Detail看到形成Layer的原因。
Demo
在section#background
加上will-change:transform;
可以在發現原本Paint整個頁面的變成只剩下Paint滾動條。
參考
Learn How to Develop the Next Generation of Applications for the Web
Website Performance Optimization
Browser Rendering Optimization