首頁 >web前端 >css教學 >使用CSS3 3D行星運作以及瀏覽器渲染原理詳細介紹

使用CSS3 3D行星運作以及瀏覽器渲染原理詳細介紹

高洛峰
高洛峰原創
2017-03-19 16:44:191787瀏覽

最近入坑 Web 動畫,所以把自己的學習過程記錄一下分享給大家。

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

本文完整的程式碼,以及更多的 CSS3 效果,在我 Github 上可以看到,也希望大家可以點個 star。

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

CSS3 3D 行星運轉效果圖片

CSS3 3D 行星运转动画,太阳系动画

隨機再截圖了一張:

CSS3 3D 行星运转动画,太阳系动画

#你點進 Demo 頁感受一下CSS3 3D 的魅力,圖片能展現的東西畢竟有限。

然後,這個 CSS3 3D 行星運轉動畫的製作過程不再詳細贅述,本篇的重點放在 Web 動畫介紹及效能優化方面。詳細的 CSS3 3D 可以回看上一篇部落格:【CSS3進階】酷炫的3D旋轉透視。簡單的想法:

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

2. 每一個球體的製作,想了許多方法,最終使用了這種折衷的方式,每個球體本身也是CSS3 3D 圖形。然後在製作過程中使用Sass 編寫CSS 可以減少很多繁瑣的編寫CSS 動畫的過程;

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)

找到了一張很經典的圖:

使用CSS3 3D行星运转以及浏览器渲染原理详细介绍

這個渲染過程作為一個基礎知識,繼續往下深入。

當頁面載入並解析完畢後,它在瀏覽器內代表了一個大家十分熟悉的結構: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 選項:

使用CSS3 3D行星运转以及浏览器渲染原理详细介绍

#在一個極簡單的頁面,我們可以看到如下圖所示,這個頁面只有一個層。藍色網格表示瓦片(tile),你可以把它們當作是層的單元(並不是層),Chrome 可以將它們作為一個大層的部分上傳給GPU:

使用CSS3 3D行星运转以及浏览器渲染原理详细介绍

元素自身層的創建

因為上面的頁面十分簡單,所以並沒有產生層,但是在很複雜的頁面中,譬如我們給元素設置一個3D CSS 屬性來變換它,我們就能看到當元素擁有自己的層時會是什麼樣子。

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

使用CSS3 3D行星运转以及浏览器渲染原理详细介绍

何時觸發建立層 ?

上面示意圖中黃色邊框框住的層,就是 GraphicsLayer ,它對於我們的Web 動畫而言非常重要,通常,Chrome 會將一個層的內容在作為紋理上傳到GPU 前先繪製(paint )進一個位圖中。如果內容不會改變,那就沒有必要重繪(repaint)層。

這樣做的意義在於:花在重繪上的時間可以用來做別的事情,例如運行 JavaScript,如果繪製的時間很長,還會造成動畫的故障與延遲。

那麼一個元素什麼時候會觸發建立一個層?從目前來說,滿足下列任意情況會建立層:

  • 3D 或透視轉換(perspective、transform) CSS 屬性

  • ##使用加速視訊解碼的39000f942b2545a5315c57fa3276f220 元素

  • 擁有3D (WebGL) 上下文或加速的2D 上下文的5049eebf73c4002660dae05da85b749c 元素
  • #混合插件(如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 inan input box)

    ##啟動CSS
  • 偽類
  • ,例如

    :hover

    (IE 中為兄弟結點偽類的激活)(Activation of CSS pseudo
  • class
  • es 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)

  • 計算offset

    Width

    和offset
  • Height
屬性(Calculating offsetWidth and offsetHeight)


設定style屬性的值(Setting a property of the style attribute)

##########所以對於頁面而言,我們的宗旨就是盡量減少頁面的回流重繪,簡單的一個栗子:###
// 下面这种方式将会导致回流reflow两次
var newWidth = ap.offsetWidth + 10; // Read
ap.style.width = newWidth + 'px'; // Write
var newHeight = ap.offsetHeight + 10; // Read
ap.style.height = newHeight + 'px'; // Write

// 下面这种方式更好,只会回流reflow一次
var newWidth = ap.offsetWidth + 10; // Read
var newHeight = ap.offsetHeight + 10; // Read
ap.style.width = newWidth + 'px'; // Write
ap.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



width,height


##請求了getComputedStyle(), 或IE的

current

Style

這個時候,瀏覽器為了回饋最精確的訊息,需要立即回流重繪一次,確保給到我們的訊息是準確的,所以可能導致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,那麼這個子孫元素又會顯現出來。

動畫的效能偵測及最佳化

消耗效能樣式

使用CSS3 3D行星运转以及浏览器渲染原理详细介绍不同樣式在消耗效能上是不同的,譬如box-shadow 從渲染角度來講十分耗效能,原因就是與其他樣式相比,它們的繪製程式碼執行時間過長。這就是說,如果一個耗性能嚴重的樣式經常需要重繪,那麼你就會遇到效能問題。其次你要知道,沒有不變的事情,在今天性能很差的樣式,可能明天就被優化,並且瀏覽器之間也存在差異。

因此關鍵在於,你要藉助開發工具來分辨出效能瓶頸所在,然後設法減少瀏覽器的工作量。 好在chrome 瀏覽器提供了許多強大的功能,讓我們可以檢測我們的動畫性能,除了上面提到的,我們還可以通過勾選下面這個show FPS meter 顯示頁面的FPS 信息,以及GPU 的使用率:

#########使用will-change 提高頁面滾動、動畫等渲染性能#########官方文件說,這是一個仍處於實驗階段的功能,所以在未來版本的瀏覽器中該功能的語法和行為可能會隨之改變。 ###

使用CSS3 3D行星运转以及浏览器渲染原理详细介绍

使用方法示例:(具体每个取值的意义,去翻翻文档)

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轴的变化,但是还是假模假样地声明了,去欺骗浏览器。

以上是使用CSS3 3D行星運作以及瀏覽器渲染原理詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn