作者: friday
http://forsomething.cn/
先總結(jié)幾個要點
-
精簡DOM,合理布局
-
使用transform代替left、top減少使用引起頁面重排的屬性
-
開啟硬件加速
-
盡量避免瀏覽器創(chuàng)建不必要的圖形層
-
盡量減少js動畫,如需要,使用對性能友好的requestAnimationFrame
-
使用chrome performance工具調(diào)試動畫性能
我們知道網(wǎng)頁動畫的每一幀都是一次重新渲染,每秒低于24鎮(zhèn)的動畫,人眼就能感受到停頓,每秒30-60幀才能比較流暢 瀏覽器會按照大多數(shù)顯示器的刷新頻率60Hz來刷新動畫, 如果想達到60FPS,就意味著每一幀的任務(wù)耗時不能高于16毫秒。
通過下圖我們可以了解瀏覽器渲染每一幀的過程

1.JavaScript。一般來說,我們會使用 JavaScript 來實現(xiàn)一些視覺變化的效果。比如用 jQuery 的 animate 函數(shù)做一個動畫、對一個數(shù)據(jù)集進行排序或者往頁面里添加一些 DOM 元素等。當然,除了 JavaScript,還有其他一些常用方法也可以實現(xiàn)視覺變化效果,比如:CSS Animations、Transitions 和 Web Animation API。
2.樣式計算。此過程是根據(jù)匹配選擇器(例如 .headline 或 .nav > .nav__item)計算出哪些元素應用哪些 CSS 3. 規(guī)則的過程。從中知道規(guī)則之后,將應用規(guī)則并計算每個元素的最終樣式。
3.布局。在知道對一個元素應用哪些規(guī)則之后,瀏覽器即可開始計算它要占據(jù)的空間大小及其在屏幕的位置。網(wǎng)頁的布局模式意味著一個元素可能影響其他元素,例如 元素的寬度一般會影響其子元素的寬度以及樹中各處的節(jié)點,因此對于瀏覽器來說,布局過程是經(jīng)常發(fā)生的。
4.繪制。繪制是填充像素的過程。它涉及繪出文本、顏色、圖像、邊框和陰影,基本上包括元素的每個可視部分。繪制一般是在多個表面(通常稱為層)上完成的。
5.合成。由于頁面的各部分可能被繪制到多層,由此它們需要按正確順序繪制到屏幕上,以便正確渲染頁面。對于與另一元素重疊的元素來說,這點特別重要,因為一個錯誤可能使一個元素錯誤地出現(xiàn)在另一個元素的上層。
“生成布局”(flow)和”繪制"(paint)這兩步,合稱為"渲染"(render)。重新渲染就是需要重新生成布局和重新繪制。 有上述的渲染流水線我們可以得知重繪不一定需要重排,重排必然導致重繪
重排和重繪會不斷觸發(fā),這是不可避免的。但是,它們非常耗費資源,是導致網(wǎng)頁性能低下的根本原因。 提高網(wǎng)頁性能,就是要降低"重排"和"重繪"的頻率和成本,盡量少觸發(fā)重新渲染。
重排還重繪會消耗大量的CPU和GPU資源,前端新能優(yōu)化最主要的優(yōu)化點就是盡量減少重繪和重排。

影響網(wǎng)頁渲染的因素
其中最簡單的,樣式表越簡單,重繪和重排越快,重繪和重排的DOM元素層級越高,成本就越高,所以我們在開發(fā)前端頁面時就需要精簡DOM元素,合理布局。
另外Table元素的重排和重繪成本要高于div,所以我們提倡使用div+css布局,盡量避免使用table布局。
還有其他對渲染性能有影響的操作,比如:
DOM元素讀寫分離
讓進行大量動畫的元素脫離文檔流,減少重排開銷
通過改變元素的class或csstext一次性的更改樣式
緩存DOM元素的位置信息,避免不必要的屬性讀取
盡量使用離線DOM
使用css3 transform優(yōu)化動畫性能
使用css3 transform
該CSS屬性可以旋轉(zhuǎn),縮放,傾斜,或者上傳給定的元素。這是通過修改CSS 可視格式模型的坐標空間來實現(xiàn)的。
如果該屬性的值不是none,則會創(chuàng)建一個堆疊上下文。在這種情況下,該對象將充當position: fixed的包含塊(所以position: fixed的元素將會被他覆蓋)。
css3 transform 的執(zhí)行效率
我們通過一個例子來解釋為什么transform的動畫執(zhí)行效果更佳。
<!-- 對應圖1-->
div { height: 100px; transition: height 1s linear; }
div:hover { height: 200px; }
<!-- 對應圖2 -->
div { transform: scale(0.5); transition: transform 1s linear; }
div:hover { transform: scale(1.0); }
一個從 height: 100px 到 height: 200px 的 動畫按照下面的流程圖來執(zhí)行各種操作 橙色方框的操作比較耗時,綠色方框的操作比較快速


因為每一幀的變化瀏覽器都在進行布局、繪制、把新的位圖交給 GPU 內(nèi)存,但是在將位圖加載到GPU內(nèi)存中的操作是個相對耗時的操作。
GPU 在如下方面很快:
- 繪制位圖到屏幕上
- 可不斷的繪制相同的位圖
- 將同一位圖進行位移、旋轉(zhuǎn)、縮放
我們看使用了transform屬性的動畫執(zhí)行過程(圖二),這個無疑是效率最優(yōu)的執(zhí)行方式。
層的引入(參考-無線性能優(yōu)化:Composite)
頁面一旦在裝入并解析完成后,就會表示為許多Web開發(fā)者所熟悉的結(jié)構(gòu):DOM。然而,在頁面的渲染過程中,瀏覽器還具有一系列并不直接暴露給開發(fā)者的頁面中間表示方式。這些表示方式中最重要的結(jié)構(gòu)就是層。
在Chrome中實際上有幾種不同類型的層:掌管DOM子樹的渲染層(RenderLayer)以及掌管渲染層子樹的圖形層(GraphicsLayer),某些特殊的渲染層會被認為是合成層(Compositing Layers,合成層擁有單獨的 GraphicsLayer。
擁有單獨GraphicsLayer的層,都會將位圖存儲在共享內(nèi)存中,作為紋理上傳到 GPU 中,最后由 GPU 將多個位圖進行合成,然后 draw 到屏幕上。
什么渲染層會提升為合成層?Chrome在這方面采用的規(guī)則仍在隨著時間推移逐漸發(fā)展變化,但在目前下面這些因素都會引起Chrome創(chuàng)建層:
- 進行3D或者透視變換的CSS屬性
- 使用硬件加速視頻解碼的<video>元素
- 具有3D(WebGL)上下文或者硬件加速的2D上下文的<canvas>元素
- 組合型插件(即Flash)
- 具有有CSS透明度動畫或者使用動畫式Webkit變換的元素
- 具有硬件加速的CSS濾鏡的元素
- 子元素中存在具有組合層的元素的元素(換句話說,就是存在具有自己的層的子元素的元素)
- 同級元素中有Z索引比其小的元素,而且該Z索引比較小的元素具有組合層(換句話說就是在組合層之上進行渲染的元素)
提升為合成層簡單說來有以下幾點好處
- 合成層的位圖,會交由 GPU 合成,比 CPU 處理要快
- 當需要 repaint 時,只需要 repaint 本身,不會影響到其他的層
- 對于 transform 和 opacity 效果,不會觸發(fā) layout 和 paint
合成層的好處是不會影響到其他元素的繪制,因此,為了減少動畫元素對其他元素的影響,從而減少 paint,我們需要把動畫效果中的元素提升為合成層。
提升合成層的最好方式是使用 CSS 的 will-change 屬性。從上一節(jié)合成層產(chǎn)生原因中,可以知道 will-change 設(shè)置為 opacity、transform、top、left、bottom、right 可以將元素提升為合成層。
對于那些目前還不支持 will-change 屬性的瀏覽器,目前常用的是使用一個 3D transform 屬性來強制提升為合成層:transofrm: translateZ(0)
通常情況下開啟硬件加速會提高動畫的流暢性,但是過多的合成層也會造成性能瓶頸,過多的合成層會占用大量的內(nèi)存空間,
對于合成層占用內(nèi)存的問題,這里有兩個 demo 進行了驗證。
demo 1 和 demo 2 中,會創(chuàng)建 2000 個同樣的 div 元素,不同的是 demo 2 中的元素通過 will-change 都提升為了合成層,而兩個 demo 頁面的內(nèi)存消耗卻有很明顯的差別。

網(wǎng)頁動畫的渲染
有一些JavaScript方法可以調(diào)節(jié)重新渲染,大幅提高網(wǎng)頁性能。
其中最重要的,就是 window.requestAnimationFrame() 方法。它可以將某些代碼放到下一次重新渲染時執(zhí)行。
window.requestAnimationFrame(fn);
**window.requestIdleCallback()**也可以用來調(diào)節(jié)重新渲染。它指定只有當一幀的末尾有空閑時間,才會執(zhí)行回調(diào)函數(shù)。只有當前幀的運行時間小于16.66ms時,函數(shù)fn才會執(zhí)行。否則,就推遲到下一幀,如果下一幀也沒有空閑時間,就推遲到下下一幀,以此類推
requestIdleCallback(fn);
它還可以接受第二個參數(shù),表示指定的毫秒數(shù)。如果在指定 的這段時間之內(nèi),每一幀都沒有空閑時間,那么函數(shù)fn將會強制執(zhí)行。
requestIdleCallback(fn, 5000);
Chrome Devtool Performance
怎么去分析頁面運行時的性能表現(xiàn),Chrome Devtool Performance是一個很好的選擇。這里請大家參考這篇文章全新Chrome Devtool Performance使用指南