CSS動畫性能——重繪與重排


身為一個前端,只考慮動畫怎樣實現就夠了么?也許后續的動畫性能優化才是你最大的敵人。。

為什么會有這篇博文,說來慚愧。雖然用過CSS3制作過大量的動畫效果,但在PC端和移動端,動畫表現時佳時不佳,會卡頓會掉幀,有大量動畫的頁面更是會使移動設備的耗電和發熱狀態達到跟玩高FPS大型手游一樣。小動畫的卡頓掉幀問題也夠讓人抓耳撓腮一段時日。這篇博客並不會給出解決方案(因為我也沒找到解決方案),因為導致動畫卡頓的原因數不勝數,比如低端安卓設備,縱使用transform,動畫還是有可能從直接從一邊運行一段時間...然后“瞬移”到另一邊...,未知因素過多,故只是在此記錄一下之前制作動畫時未考慮到的知識。

一、瀏覽器渲染流程

說到動畫性能,就不得不提到頁面的渲染流程

  1. 解析HTML,創建DOM樹
  2. 解析CSS,生成CSS規則樹
  3. 將DOM樹與CSS規則樹合並,構建渲染樹(RenderingObject樹)
  4. 布局和繪制,重繪(repaint)和重排(reflow) (重排也稱回流)

二、重繪和重排

對動畫性能影響最大的,就是重繪和重排。且重排的代價比重繪要大。重排的花銷跟render tree有多少節點需要重新構建有關系,假如在body最前面插入一個元素,會導致整個render tree回流,但如果是指body后面插入一個元素,則不會影響前面的元素重排。

1. 當頁面布局和幾何屬性改變時就需要重排。下述情況會發生瀏覽器重排

  • 添加或者刪除可見的DOM元素
  • 元素位置改變
  • 元素尺寸改變(包括:內外邊距、邊框厚度、寬度和高度等屬性的改變)
  • 內容改變,例如:文本改變或者圖片被另一個不同尺寸的圖片替代
  • 頁面渲染器初始化
  • 瀏覽器窗口尺寸改變
  • 對可見元素 display:none,或者對不可見元素 display:block 時
  • 激活偽類(:hover)
  • transition對寬高的處理,在整個transition的每一幀中,瀏覽器都要去重新布局,繪制頁面(參考)

 根據改變的范圍和程度,渲染樹中或大或小的對應的部分也需要重新計算。有些改變會觸發整個頁面的重排:例如,當滾動條出現時。

 瀏覽器重排必定導致重繪,但重繪不一定導致重排。

2. 重繪何時發生

  當 render tree 中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀、風格,而不會影響布局的,比如 background-color,則稱之為重繪。

  • 改變字體
  • 增加或者移除樣式表
  • 內容變化,比如用戶在input框中輸入文字
  • 激活CSS偽類(:hover)
  • 腳本操作DOM (也有可能造成回流)
  • 計算 offsetWidth 和 offsetHeight 的屬性
  • 設置style屬性的值

3.渲染樹變化的排隊與刷新

  由於每次重排都會產生計算損耗,大多數瀏覽器通過隊列化修改並批量執行來優化重排過程。然而你可能會(經常不知不覺)強制刷新隊列並要求計划任務立即執行。獲取布局信息的操作會導致隊列刷新,比如以下方法:

  • offsetTop,offsetLeft,offsetWidth,offsetHeight
  • scrollTop,scrollLeft,scrollWidth,scrollHeight
  • clientTop,clientLeft,clientWidth,clientHeight
  • width,height
  • getComputedStyle() (currentStyle in IE)
  • JS更改元素style

 以上屬性和方法需要返回最新的布局信息,因此瀏覽器不得不執行渲染隊列中的“待處理”變化並觸發重排以返回正確的值。

 在修改樣式的過程中,最好避免使用上面列出的屬性。它們都會刷新渲染隊列,即使你是在獲取最近未發生改變的或者與最新變化無關的布局信息。

4. 最小化重繪和重排

  • 最小化DOM訪問次數,盡可能在JavaScript端處理
  • 如果需要多次訪問某個DOM節點,請使用局部變量存儲它的引用
  • 小心處理HTML集合,因為它實時聯系着底層文檔。把集合長度緩存到一個變量中,並在迭代中使用它
  • 如果可能的話,使用速度更快的API,比如 querySelectorAll() 和 firstElementChild
  • 要留意重繪和重排:批量修改樣式時,“離線”操作DOM樹,使用緩存,並減少訪問布局信息的次數
  • 動畫中使用絕對定位
  • 使用事件委托來減少事件處理器的數量
  • 盡量避免用 transition 過渡會更改布局的屬性,如果有位移之類的,考慮用transform + transition
  • 制作動畫時,盡量使用 CSS3 的 transform,因為 transform 屬性不會改變元素的布局(更詳細的知識可以參考:詳談層合成composite 

三、小動畫卡頓解決方案

之前做過一個小動畫,是一個元素的寬度由 0px 變到 40px,所經歷時長是7s。我是怎么寫的呢,常規思路:

.block{
    animation: change 7s linear;
}

@keyframes change{
    from{ margin-left: 0px; }
    to{ margin-left: -40px; }
}

結果動畫看起來很“卡”,在我看了上述重繪、重排的知識后,以為定是自己寫的動畫性能忒差導致的,遂按照上述規則改進了一下,將.block設置為了絕對定位,使其脫離文檔流,margin-left 改成 left,無果,依然卡。

仔細一想,不對勁,雖然這個小動畫性能不佳,但是整個頁面只有這一個動畫。仔細看了下自己定義的動畫規則,7s變化40px....是否是間隔太長的緣故...遂將時長設置為4s,卡頓緩和了許多,設置時長越短,卡頓越不明顯。

但關鍵是,要求制作的效果就是要7s變化40px,遂改用transform:translateX()。終於,在7s改變40px的情況下,也能做到絲滑流暢了。

但為什么transform在7s使就可以做到絲滑流暢,這里有兩點猜想:有可能是因為transform開啟了GPU加速,有可能動畫對transform屬性和非transform屬性的渲染幀數不一樣。但到底是為何,還有待求證。

但不管是因為什么,請記住這句話,動畫配合transform食用更佳

 

*參考:

  《高性能的JavaScript》——Nicholas C.Zakas

css重排與重繪

頁面重繪和回流以及優化

詳解瀏覽器渲染頁面過程

高性能JavaScript重排與重繪

回流與重繪:CSS性能讓JavaScript變慢? 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM