高性能 CSS3 動畫

高性能 CSS3 動畫

本帖最後由 quanyaji 於 2013-8-14 16:29 編輯

https://github.com/AlloyTeam/Mar ... e-css3-animation.mdcss

  1. 高性能 CSS3 動畫

  2. 高性能移動Web相較PC的場景須要考慮的因素也相對更多更復雜,咱們總結爲如下幾點: 流量、功耗與流暢度。 在PC時代咱們更多的是考慮體驗上的流暢度,而在Mobile端自己豐富的場景下,須要額外關注對用戶基站網絡流量使用的狀況,設備耗電量的狀況。

  3. 關於流暢度,主要體如今前端動畫中,在現有的前端動畫體系中,一般有兩種模式:JS動畫與CSS3動畫。 JS動畫是經過JS動態改寫樣式實現動畫能力的一種方案,在PC端兼容低端瀏覽器中不失爲一種推薦方案。 而在移動端,咱們選擇性能更優瀏覽器原生實現方案:CSS3動畫。

  4. 然而,CSS3動畫在移動多終端設備場景下,相比PC會面對更多的性能問題,主要體如今動畫的卡頓與閃爍。

  5. 目前對提高移動端CSS3動畫體驗的主要方法有幾點:

  6. 儘量多的利用硬件能力,如使用3D變形來開啓GPU加速

  7. -webkit-transform: translate3d(0, 0, 0);
  8. -moz-transform: translate3d(0, 0, 0);
  9. -ms-transform: translate3d(0, 0, 0);
  10. transform: translate3d(0, 0, 0);
  11. 如動畫過程有閃爍(一般發生在動畫開始的時候),能夠嘗試下面的Hack:

  12. -webkit-backface-visibility: hidden;
  13. -moz-backface-visibility: hidden;
  14. -ms-backface-visibility: hidden;
  15. backface-visibility: hidden;

  16. -webkit-perspective: 1000;
  17. -moz-perspective: 1000;
  18. -ms-perspective: 1000;
  19. perspective: 1000;
  20. 以下面一個元素經過translate3d右移500px的動畫流暢度會明顯優於使用left屬性:

  21. #ball-1 {
  22.   transition: -webkit-transform .5s ease;
  23.   -webkit-transform: translate3d(0, 0, 0);
  24. }
  25. #ball-1.slidein {
  26.   -webkit-transform: translate3d(500px, 0, 0);
  27. }


  28. #ball-2 {
  29.   transition: left .5s ease;
  30.   left: 0;
  31. }
  32. #ball-2.slidein {
  33.   left: 500px;
  34. }
  35. 注:3D變形會消耗更多的內存與功耗,應確實有性能問題時纔去使用它,兼在權衡

  36. 儘量少的使用box-shadows與gradients

  37. box-shadows與gradients每每都是頁面的性能殺手,尤爲是在一個元素同時都使用了它們,因此擁抱扁平化設計吧。

  38. 儘量的讓動畫元素不在文檔流中,以減小重排

  39. position: fixed;
  40. position: absolute;
  41. 優化 DOM layout 性能

  42. 咱們從實例開始描述這個主題:

  43. var newWidth = aDiv.offsetWidth + 10;
  44. aDiv.style.width = newWidth + 'px';
  45. var newHeight = aDiv.offsetHeight + 10;
  46. aDiv.style.height = newHeight + 'px';

  47. var newWidth = aDiv.offsetWidth + 10;
  48. var newHeight = aDiv.offsetHeight + 10;
  49. aDiv.style.width = newWidth + 'px';
  50. aDiv.style.height = newHeight + 'px';
  51. 這是兩段能力上徹底等同的代碼,顯式的差別正如咱們所見,只有執行順序的區別。但真是如此嗎?下面是加了說明註釋的代碼版本,很好的闡述了其中的進一步差別:

  52. // 觸發兩次 layout
  53. var newWidth = aDiv.offsetWidth + 10;   // Read
  54. aDiv.style.width = newWidth + 'px';     // Write
  55. var newHeight = aDiv.offsetHeight + 10; // Read
  56. aDiv.style.height = newHeight + 'px';   // Write

  57. // 只觸發一次 layout
  58. var newWidth = aDiv.offsetWidth + 10;   // Read
  59. var newHeight = aDiv.offsetHeight + 10; // Read
  60. aDiv.style.width = newWidth + 'px';     // Write
  61. aDiv.style.height = newHeight + 'px';   // Write
  62. 從註釋中可找到規律,連續的讀取offsetWidth/Height屬性與連續的設置width/height屬性,相比分別讀取設置單個屬性可少觸發一次layout。

  63. 從結論看彷佛與執行隊列有關,沒錯,這是瀏覽器的優化策略。全部可觸發layout的操做都會被暫時放入 layout-queue 中,等到必須更新的時候,再計算整個隊列中全部操做影響的結果,如此就可只進行一次的layout,從而提高性能。

  64. 關鍵一,可觸發layout的操做,哪些操做下會layout的更新(也稱爲reflow或者relayout)?

  65. 咱們從瀏覽器的源碼實現入手,以開源Webkit/Blink爲例, 對layout的更新,Webkit 主要經過 Document::updateLayout 與 Document::updateLayoutIgnorePendingStylesheets 兩個方法:

  66. void Document::updateLayout()
  67. {
  68.     ASSERT(isMainThread());

  69.     FrameView* frameView = view();
  70.     if (frameView && frameView->isInLayout()) {
  71.         ASSERT_NOT_REACHED();
  72.         return;
  73.     }

  74.     if (Element* oe = ownerElement())
  75.         oe->document()->updateLayout();

  76.     updateStyleIfNeeded();

  77.     StackStats::LayoutCheckPoint layoutCheckPoint;

  78.     if (frameView && renderer() && (frameView->layoutPending() || renderer()->needsLayout()))
  79.         frameView->layout();

  80.     if (m_focusedNode && !m_didPostCheckFocusedNodeTask) {
  81.         postTask(CheckFocusedNodeTask::create());
  82.         m_didPostCheckFocusedNodeTask = true;
  83.     }
  84. }


  85. void Document::updateLayoutIgnorePendingStylesheets()
  86. {
  87.     bool oldIgnore = m_ignorePendingStylesheets;

  88.     if (!haveStylesheetsLoaded()) {
  89.         m_ignorePendingStylesheets = true;

  90.         HTMLElement* bodyElement = body();
  91.         if (bodyElement && !bodyElement->renderer() && m_pendingSheetLayout == NoLayoutWithPendingSheets) {
  92.             m_pendingSheetLayout = DidLayoutWithPendingSheets;
  93.             styleResolverChanged(RecalcStyleImmediately);
  94.         } else if (m_hasNodesWithPlaceholderStyle)
  95.             recalcStyle(Force);
  96.     }

  97.     updateLayout();

  98.     m_ignorePendingStylesheets = oldIgnore;
  99. }
  100. 從 updateLayoutIgnorePendingStylesheets 方法的內部實現可知,其也是對 updateLayout 方法的擴展,而且在現有的 layout 更新模式中,大部分場景都是調用 updateLayoutIgnorePendingStylesheets 來進行layout的更新。

  101. 搜索 Webkit 實現中調用 updateLayoutIgnorePendingStylesheets 方法的代碼, 獲得如下可致使觸發 layout 的操做:

  102. Element: clientHeight, clientLeft, clientTop, clientWidth, focus(), getBoundingClientRect(), getClientRects(), innerText, offsetHeight, offsetLeft, offsetParent, offsetTop, offsetWidth, outerText, scrollByLines(), scrollByPages(), scrollHeight, scrollIntoView(), scrollIntoViewIfNeeded(), scrollLeft, scrollTop, scrollWidth

  103. Frame, HTMLImageElement: height, width

  104. Range: getBoundingClientRect(), getClientRects()

  105. SVGLocatable: computeCTM(), getBBox()

  106. SVGTextContent: getCharNumAtPosition(), getComputedTextLength(), getEndPositionOfChar(), getExtentOfChar(), getNumberOfChars(), getRotationOfChar(), getStartPositionOfChar(), getSubStringLength(), selectSubString()

  107. SVGUse: instanceRoot

  108. window: getComputedStyle(), scrollBy(), scrollTo(), scrollX, scrollY, webkitConvertPointFromNodeToPage(), webkitConvertPointFromPageToNode()

  109. 進一步深刻Layout,那上文中必須更新的必要條件是什麼? 在 Stoyan Stefanov 的 Rendering: repaint, reflow/relayout, restyle 一文中已作比較詳細的解答,可移步瞭解~
複製代碼
相關文章
相關標籤/搜索