【Web動畫】CSS3 3D 行星運轉 && 瀏覽器渲染原理

原文: 【Web動畫】CSS3 3D 行星運轉 && 瀏覽器渲染原理

承接上一篇:【CSS3進階】酷炫的3D旋轉透視 。javascript

最近入坑 Web 動畫,因此把本身的學習過程記錄一下分享給你們。php

CSS3 3D 行星運轉 demo 頁面請戳:Demo。(建議使用Chrome打開)css

本文完整的代碼,以及更多的 CSS3 效果,在我 Github 上能夠看到,也但願你們能夠點個 star。html

嗯,可能有些人打不開 demo 或者頁面亂了,貼幾張效果圖:(圖片有點大,耐心等待一會)html5

CSS3 3D 行星運轉效果圖

CSS3 3D 行星運轉動畫,太陽系動畫

隨機再截屏了一張:java

CSS3 3D 行星運轉動畫,太陽系動畫

強烈建議你點進 Demo 頁感覺一下 CSS3 3D 的魅力,圖片能展示的東西畢竟有限。css3

而後,這個 CSS3 3D 行星運轉動畫的製做過程再也不詳細贅述,本篇的重點放在 Web 動畫介紹及性能優化方面。詳細的 CSS3 3D 能夠回看上一篇博客:【CSS3進階】酷炫的3D旋轉透視。簡單的思路:git

1. 利用上一篇所製做的 3D 照片牆爲原型,改造而來;github

2. 每個球體的製做,想了許多方法,最終使用了這種折中的方式,每個球體自己也是一個 CSS3 3D 圖形。而後在製做過程當中使用 Sass 編寫 CSS 能夠減小不少繁瑣的編寫 CSS 動畫的過程;web

3. Demo 當中有使用 Javascript 寫了一個鼠標跟隨的監聽事件,去掉這個事件,整個行星運動動畫自己是純 CSS 實現的。

 

下面將進入本文的重點,從性能優化的角度講講瀏覽器渲染展現原理,瀏覽器的重繪與重排,動畫的性能檢測優化等:

 

   瀏覽器渲染展現原理 及 對web動畫的影響

小標題起得有點大,咱們知道,不一樣瀏覽器的內核(渲染引擎,Rendering Engine)是不同的,例如如今最主流的 chrome 瀏覽器的內核是 Blink 內核(在Chrome(28及日後版本)、Opera(15及日後版本)和Yandex瀏覽器中使用),火狐是 Gecko,IE 是 Trident ,瀏覽器內核負責對網頁語法的解釋並渲染(顯示)網頁,不一樣瀏覽器內核的工做原理並不徹底一致。

因此其實下面將主要討論的是 chrome 瀏覽器下的渲染原理。由於 chrome 內核渲染可查證的資料較多,對於其餘內核的瀏覽器不敢妄下定論,因此下面展開的討論默認是針對 chrome 瀏覽器的。

首先,我要拋出一點結論:

使用 transform3d api 代替 transform api,強制開始 GPU 加速

這裏談到了 GPU 加速,爲何 GPU 可以加速 3D 變換?這一切又必需要從瀏覽器底層的渲染講起,瀏覽器渲染展現網頁的過程,老生常談,面試必問,大體分爲:

1. 解析HTML(HTML Parser)

2. 構建DOM樹(DOM Tree)

3. 渲染樹構建(Render Tree)

4. 繪製渲染樹(Painting)

簡單解釋一下,經過請求獲得的 HTML 通過解析(HTML parser)生成 DOM Tree。而在 CSS 解析完畢後,須要將解析的結果與 DOM Tree 的內容一塊兒進行分析創建一棵 Render Tree,最終用來進行繪圖(Painting)。

找到了一張很經典的圖:

瀏覽器渲染頁面過程

這個渲染過程做爲一個基礎知識,繼續往下深刻。

當頁面加載並解析完畢後,它在瀏覽器內表明了一個你們十分熟悉的結構:DOM(Document Object Model,文檔對象模型)。在瀏覽器渲染一個頁面時,它使用了許多沒有暴露給開發者的中間表現形式,其中最重要的結構即是(layer)。

這個層就是本文重點要討論的內容:

而在 Chrome 中,存在有不一樣類型的層: RenderLayer(負責 DOM 子樹),GraphicsLayer(負責 RenderLayer 的子樹)。接下來咱們所討論的將是 GraphicsLayer 層。

GraphicsLayer 層是做爲紋理(texture)上傳給 GPU 的。

這裏這個紋理很重要,那麼,

什麼是紋理(texture)

這裏的紋理指的是 GPU 的一個術語:能夠把它想象成一個從主存儲器(例如 RAM)移動到圖像存儲器(例如 GPU 中的 VRAM)的位圖圖像(bitmap image)。一旦它被移動到 GPU 中,你能夠將它匹配成一個網格幾何體(mesh geometry),在 Chrome 中使用紋理來從 GPU 上得到大塊的頁面內容。經過將紋理應用到一個很是簡單的矩形網格就能很容易匹配不一樣的位置(position)和變形(transformation),這也就是 3D CSS 的工做原理。

提及來很難懂,直接看例子,在 chrome 中,咱們是能夠看到上文所述的 GraphicsLayer -- 層的概念。在開發者工具中,咱們進行以下選擇調出 show layer borders 選項:

在一個極簡單的頁面,咱們能夠看到以下所示,這個頁面只有一個層。藍色網格表示瓦片(tile),你能夠把它們看成是層的單元(並非層),Chrome 能夠將它們做爲一個大層的部分上傳給 GPU:

元素自身層的建立

由於上面的頁面十分簡單,因此並無產生層,可是在很複雜的頁面中,譬如咱們給元素設置一個 3D CSS 屬性來變換它,咱們就能看到當元素擁有本身的層時是什麼樣子。

注意橘黃色的邊框,它畫出了該視圖中層的輪廓:

 

什麼時候觸發建立層 ?

上面示意圖中黃色邊框框住的層,就是 GraphicsLayer ,它對於咱們的 Web 動畫而言很是重要,一般,Chrome 會將一個層的內容在做爲紋理上傳到 GPU 前先繪製(paint)進一個位圖中。若是內容不會改變,那麼就沒有必要重繪(repaint)層。

這樣作的意義在於:花在重繪上的時間能夠用來作別的事情,例如運行 JavaScript,若是繪製的時間很長,還會形成動畫的故障與延遲。

那麼一個元素何時會觸發建立一個層?從目前來講,知足如下任意狀況便會建立層:

  • 3D 或透視變換(perspective、transform) CSS 屬性
  • 使用加速視頻解碼的 <video> 元素
  • 擁有 3D (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 混合插件(如 Flash)
  • 對本身的 opacity 作 CSS 動畫或使用一個動畫變換的元素
  • 擁有加速 CSS 過濾器的元素
  • 元素有一個包含複合層的後代節點(換句話說,就是一個元素擁有一個子元素,該子元素在本身的層裏)
  • 元素有一個 z-index 較低且包含一個複合層的兄弟元素(換句話說就是該元素在複合層上面渲染)

 

層的重繪

對於靜態 Web 頁面而言,層在第一次被繪製出來以後將不會被改變,但對於 Web 動畫,頁面的 DOM 元素是在不斷變換的,若是層的內容在變換過程當中發生了改變,那麼層將會被重繪(repaint)。

強大的 chrome 開發者工具提供了工具讓咱們能夠查看到動畫頁面運行中,哪些內容被從新繪製了:

在舊版的 chrome 中,是有 show paint rects 這一個選項的,能夠查看頁面有哪些層被重繪了,並以紅色邊框標識出來。

可是新版的 chrome 貌似把這個選項移除了,如今的選項是 enable paint flashing ,其做用也是標識出網站動態變換的地方,而且以綠色邊框標識出來。

看上面的示意圖,能夠看到頁面中有幾處綠色的框,表示發生了重繪。注意 Chrome 並不會始終重繪整個層,它會嘗試智能的去重繪 DOM 中失效的部分。

按照道理,頁面發生這麼多動畫,重繪應該很頻繁纔對,可是上圖個人行星動畫中我只看到了寥寥綠色重繪框,個人我的理解是,一是 GPU 優化,二是若是整個動畫頁面只有一個層,那麼運用了 transform 進行變換,頁面必然須要重繪,可是採用分層(GraphicsLayer )技術,也就是上面說符合狀況的元素各自建立層,那麼一個元素所建立的層運用 transform 變換,譬如 rotate 旋轉,這個時候該層的旋轉變換並無影響到其餘層,那麼該層不必定須要被重繪。(我的之見,還請提出指正)。

瞭解層的重繪對 Web 動畫的性能優化相當重要。

是什麼緣由致使失效(invalidation)進而強制重繪的呢?這個問題很難詳盡回答,由於存在大量致使邊界失效的狀況。最多見的狀況就是經過操做 CSS 樣式來修改 DOM 或致使重排。

查找引起重繪和重排根源的最好辦法就是使用開發者工具的時間軸和 enable paint flashing 工具,而後試着找出剛好在重繪/重排前修改了 DOM 的地方。

總結

那麼瀏覽器是如何從 DOM 元素到最終動畫的展現呢?

  • 瀏覽器解析 HTML 獲取 DOM 後分割爲多個圖層(GraphicsLayer)
  • 對每一個圖層的節點計算樣式結果(Recalculate style--樣式重計算)
  • 爲每一個節點生成圖形和位置(Layout--迴流和重佈局)
  • 將每一個節點繪製填充到圖層位圖中(Paint Setup和Paint--重繪)
  • 圖層做爲紋理(texture)上傳至 GPU
  • 符合多個圖層到頁面上生成最終屏幕圖像(Composite Layers--圖層重組)

Web 動畫很大一部分開銷在於層的重繪,以層爲基礎的複合模型對渲染性能有着深遠的影響。當不須要繪製時,複合操做的開銷能夠忽略不計,所以在試着調試渲染性能問題時,首要目標就是要避免層的重繪。那麼這就給動畫的性能優化提供了方向,減小元素的重繪與迴流。

 

   迴流(reflow)與重繪(repaint)

這裏首先要分清兩個概念,重繪與迴流。

迴流(reflow)

當渲染樹(render Tree)中的一部分(或所有)由於元素的規模尺寸,佈局,隱藏等改變而須要從新構建。這就稱爲迴流(reflow),也就是從新佈局(relayout)。

每一個頁面至少須要一次迴流,就是在頁面第一次加載的時候。在迴流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並從新構造這部分渲染樹,完成迴流後,瀏覽器會從新繪製受影響的部分到屏幕中,該過程成爲重繪。

重繪(repaint)

當render tree中的一些元素須要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響佈局的,好比 background-color 。則就叫稱爲重繪。

值得注意的是,迴流必將引發重繪,而重繪不必定會引發迴流。

明顯,迴流的代價更大,簡單而言,當操做元素會使元素修改它的大小或位置,那麼就會發生迴流。

迴流什麼時候觸發:

  • 調整窗口大小(Resizing the window)
  • 改變字體(Changing the font)
  • 增長或者移除樣式表(Adding or removing a stylesheet)
  • 內容變化,好比用戶在input框中輸入文字(Content changes, such as a user typing text in
  • an input box)
  • 激活 CSS 僞類,好比 :hover (IE 中爲兄弟結點僞類的激活)(Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling))
  • 操做 class 屬性(Manipulating the class attribute)
  • 腳本操做 DOM(A script manipulating the DOM)
  • 計算 offsetWidth 和 offsetHeight 屬性(Calculating offsetWidth and offsetHeight)
  • 設置 style 屬性的值 (Setting a property of the style attribute)

因此對於頁面而言,咱們的宗旨就是儘可能減小頁面的迴流重繪,簡單的一個栗子:

// 下面這種方式將會致使迴流reflow兩次
var newWidth = aDiv.offsetWidth + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.height = newHeight + 'px'; // Write

// 下面這種方式更好,只會迴流reflow一次
var newWidth = aDiv.offsetWidth + 10; // Read
var newHeight = aDiv.offsetHeight + 10; // Read
aDiv.style.width = newWidth + 'px'; // Write
aDiv.style.height = newHeight + 'px'; // Write

上面四句,由於涉及了 offsetHeight 操做,瀏覽器強制 reflow 了兩次,而下面四句合併了 offset 操做,因此減小了一次頁面的迴流。 

減小回流、重繪其實就是須要減小對渲染樹的操做(合併屢次多DOM和樣式的修改),並減小對一些style信息的請求,儘可能利用好瀏覽器的優化策略。

flush隊列

其實瀏覽器自身是有優化策略的,若是每句 Javascript 都去操做 DOM 使之進行迴流重繪的話,瀏覽器可能就會受不了。因此不少瀏覽器都會優化這些操做,瀏覽器會維護 1 個隊列,把全部會引發迴流、重繪的操做放入這個隊列,等隊列中的操做到了必定的數量或者到了必定的時間間隔,瀏覽器就會 flush 隊列,進行一個批處理。這樣就會讓屢次的迴流、重繪變成一次迴流重繪。

可是也有例外,由於有的時候咱們須要精確獲取某些樣式信息,下面這些:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • width,height
  • 請求了getComputedStyle(), 或者 IE的 currentStyle

這個時候,瀏覽器爲了反饋最精確的信息,須要當即迴流重繪一次,確保給到咱們的信息是準確的,因此可能致使 flush 隊列提早執行了。

display:none 與 visibility:hidden 的異同

二者均可以在頁面上隱藏節點。不一樣之處在於,

  • display:none 隱藏後的元素不佔據任何空間。它的寬度、高度等各類屬性值都將「丟失」
  • visibility:hidden 隱藏的元素空間依舊存在。它仍具備高度、寬度等屬性值

從性能的角度而言,便是迴流與重繪的方面,

  • display:none  會觸發 reflow(迴流)
  • visibility:hidden  只會觸發 repaint(重繪),由於沒有發現位置變化

他們二者在優化中 visibility:hidden 會顯得更好,由於咱們不會由於它而去改變了文檔中已經定義好的顯示層次結構了。

對子元素的影響:

  • display:none 一旦父節點元素應用了 display:none,父節點及其子孫節點元素所有不可見,並且不管其子孫元素如何設置 display 值都沒法顯示;
  • visibility:hidden 一旦父節點元素應用了 visibility:hidden,則其子孫後代也都會所有不可見。不過存在隱藏「失效」的狀況。當其子孫元素應用了 visibility:visible,那麼這個子孫元素又會顯現出來。

 

   動畫的性能檢測及優化

 

耗性能樣式

比錯誤放置的動畫更糟的事情是致使頁面卡頓的動畫。 這將讓用戶以爲失望和不悅,而且可能但願您根本沒必要費心去設置動畫!

不一樣樣式在消耗性能方面是不一樣的,改變一些屬性的開銷比改變其餘屬性要多,所以更可能使動畫卡頓。

例如,與改變元素的文本顏色相比,改變元素的 box-shadow 將須要開銷大不少的繪圖操做。 改變元素的 width 可能比改變其 transform 要多一些開銷。如 box-shadow 屬性,從渲染角度來說十分耗性能,緣由就是與其餘樣式相比,它們的繪製代碼執行時間過長。這就是說,若是一個耗性能嚴重的樣式常常須要重繪,那麼你就會遇到性能問題。其次你要知道,沒有不變的事情,在今天性能不好的樣式,可能明天就被優化,而且瀏覽器之間也存在差別。

所以關鍵在於,你要藉助開發工具來分辨出性能瓶頸所在,而後設法減小瀏覽器的工做量。

好在 chrome 瀏覽器提供了許多強大的功能,讓咱們能夠檢測咱們的動畫性能,除了上面提到的,咱們還能夠經過勾選下面這個 show FPS meter 顯示頁面的 FPS 信息,以及 GPU 的使用率:

 

使用 will-change 提升頁面滾動、動畫等渲染性能

官方文檔說,這是一個仍處於實驗階段的功能,因此在將來版本的瀏覽器中該功能的語法和行爲可能隨之改變。

使用方法示例:(具體每一個取值的意義,去翻翻文檔)

will-change: auto
will-change: scroll-position
will-change: contents
will-change: transform        // Example of <custom-ident> 
will-change: opacity          // Example of <custom-ident>
will-change: left, top        // Example of two <animateable-feature>

will-change: unset
will-change: initial
will-change: inherit

// 示例
.example{
    will-change: transform;
}

will-change 爲 web 開發者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器能夠在元素屬性真正發生變化以前提早作好對應的優化準備工做。 這種優化能夠將一部分複雜的計算工做提早準備好,使頁面的反應更爲快速靈敏。

值得注意的是,用好這個屬性並非很容易:

  • 不要將 will-change 應用到太多元素上:瀏覽器已經盡力嘗試去優化一切能夠優化的東西了。有一些更強力的優化,若是與 will-change 結合在一塊兒的話,有可能會消耗不少機器資源,若是過分使用的話,可能致使頁面響應緩慢或者消耗很是多的資源。
  • 有節制地使用:一般,當元素恢復到初始狀態時,瀏覽器會丟棄掉以前作的優化工做。可是若是直接在樣式表中顯式聲明瞭 will-change 屬性,則表示目標元素可能會常常變化,瀏覽器會將優化工做保存得比以前更久。因此最佳實踐是當元素變化以前和以後經過腳原本切換 will-change 的值。
  • 不要過早應用 will-change 優化:若是你的頁面在性能方面沒什麼問題,則不要添加 will-change 屬性來榨取一丁點的速度。 will-change 的設計初衷是做爲最後的優化手段,用來嘗試解決現有的性能問題。它不該該被用來預防性能問題。過分使用 will-change 會致使大量的內存佔用,並會致使更復雜的渲染過程,由於瀏覽器會試圖準備可能存在的變化過程。這會致使更嚴重的性能問題。
  • 給它足夠的工做時間:這個屬性是用來讓頁面開發者告知瀏覽器哪些屬性可能會變化的。而後瀏覽器能夠選擇在變化發生前提早去作一些優化工做。因此給瀏覽器一點時間去真正作這些優化工做是很是重要的。使用時須要嘗試去找到一些方法提早必定時間獲知元素可能發生的變化,而後爲它加上 will-change 屬性。

 

使用 transform3d api 代替 transform api,強制開始 GPU 加速

GPU 可以加速 Web 動畫,這個上文已經反覆提到了。

3D transform 會啓用GPU加速,例如 translate3D, scaleZ 之類,固然咱們的頁面可能並無 3D 變換,可是不表明咱們不能啓用 GPU 加速,在非 3D 變換的頁面也使用 3D transform 來操做,算是一種 hack 加速法。咱們實際上不須要z軸的變化,可是仍是假模假樣地聲明瞭,去欺騙瀏覽器。

 

參考文獻:

Rendering: repaint, reflow/relayout, restyle

Scrolling Performance

MDN--will-change

How (not) to trigger a layout in WebKit

High Performance Animations

Accelerated Rendering in Chrome

CSS3 製做3D旋轉球體

 

到此本文結束,若是還有什麼疑問或者建議,能夠多多交流,原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。

CSS3 3D 行星運轉 demo 頁面請戳:Demo。(建議使用Chrome打開)

本文完整的代碼,以及更多的 CSS3 效果,在我 Github 上能夠看到,也但願你們能夠點個 star。

若是本文對你有幫助,請點下推薦,寫文章不容易。

相關文章
相關標籤/搜索