您好,登錄后才能下訂單哦!
本篇內容介紹了“CSS3 3D行星運轉與瀏覽器渲染的原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
隨機再截屏了一張:
強烈建議你點進 Demo - CSS3 3D 行星運轉[2] 頁感受一下 CSS3 3D 的魅力,圖片能展現的東西畢竟有限。
然后,這個 CSS3 3D 行星運轉動畫的制作過程不再詳細贅述,本篇的重點放在 Web 動畫介紹及性能優化方面。詳細的 CSS3 3D 可以回看上一篇博客:【CSS3進階】酷炫的3D旋轉透視[3]。簡單的思路:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
利用上一篇所制作的 3D 照片墻為原型,改造而來;
每一個球體的制作,想了許多方法,最終使用了這種折中的方式,每一個球體本身也是一個 CSS3 3D 圖形。然后在制作過程中使用 Sass 編寫 CSS 可以減少很多繁瑣的編寫 CSS 動畫的過程;
Demo 當中有使用 Javascript 寫了一個鼠標跟隨的監聽事件,去掉這個事件,整個行星運動動畫本身是純 CSS 實現的。
下面將進入本文的重點,從性能優化的角度講講瀏覽器渲染展示原理,瀏覽器的重繪與重排,動畫的性能檢測優化等:
小標題起得有點大,我們知道,不同瀏覽器的內核(渲染引擎,Rendering Engine)是不一樣的,例如現在最主流的 chrome 瀏覽器的內核是 Blink 內核(在Chrome(28及往后版本)、Opera(15及往后版本)和Yandex瀏覽器中使用),火狐是 Gecko,IE 是 Trident 。
瀏覽器內核負責對網頁語法的解釋并渲染(顯示)網頁,不同瀏覽器內核的工作原理并不完全一致。
所以其實下面將主要討論的是 chrome 瀏覽器下的渲染原理。因為 chrome 內核渲染可查證的資料較多,對于其他內核的瀏覽器不敢妄下定論,所以下面展開的討論默認是針對 chrome 瀏覽器的。
首先,我要拋出一點結論:
這里談到了 GPU 加速,為什么 GPU 能夠加速 3D 變換?這一切又必須要從瀏覽器底層的渲染講起,瀏覽器渲染展示網頁的過程,老生常談,面試必問,大致分為:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
解析HTML(HTML Parser)
構建DOM樹(DOM Tree)
渲染樹構建(Render Tree)
繪制渲染樹(Painting)
找到了一張很經典的圖:
瀏覽器渲染頁面過程
這個渲染過程作為一個基礎知識,繼續往下深入。
當頁面加載并解析完畢后,它在瀏覽器內代表了一個大家十分熟悉的結構:DOM(Document Object Model,文檔對象模型)。在瀏覽器渲染一個頁面時,它使用了許多沒有暴露給開發者的中間表現形式,其中最重要的結構便是層(layer)。
這個層就是本文重點要討論的內容:
而在 Chrome 中,存在有不同類型的層:RenderLayer(負責 DOM 子樹),GraphicsLayer(負責 RenderLayer 的子樹)。接下來我們所討論的將是 GraphicsLayer 層。
GraphicsLayer 層是作為紋理(texture)上傳給 GPU 的。
這里這個紋理很重要,那么,
這里的紋理指的是 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 屬性
使用加速視頻解碼的
擁有 3D (WebGL) 上下文或加速的 2D 上下文的 元素
混合插件(如 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 動畫很大一部分開銷在于層的重繪,以層為基礎的復合模型對渲染性能有著深遠的影響。當不需要繪制時,復合操作的開銷可以忽略不計,因此在試著調試渲染性能問題時,首要目標就是要避免層的重繪。那么這就給動畫的性能優化提供了方向,減少元素的重繪與回流。
這里首先要分清兩個概念,重繪與回流。
當渲染樹(render Tree)中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變而需要重新構建。這就稱為回流(reflow),也就是重新布局(relayout)。
每個頁面至少需要一次回流,就是在頁面第一次加載的時候。在回流的時候,瀏覽器會使渲染樹中受到影響的部分失效,并重新構造這部分渲染樹,完成回流后,瀏覽器會重新繪制受影響的部分到屏幕中,該過程成為重繪。
當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信息的請求,盡量利用好瀏覽器的優化策略。
其實瀏覽器自身是有優化策略的,如果每句 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 會觸發 reflow(回流)
visibility:hidden 只會觸發 repaint(重繪),因為沒有發現位置變化
他們兩者在優化中 visibility:hidden 會顯得更好,因為我們不會因為它而去改變了文檔中已經定義好的顯示層次結構了。
對子元素的影響:
display:none 一旦父節點元素應用了 display:none,父節點及其子孫節點元素全部不可見,而且無論其子孫元素如何設置 display 值都無法顯示;
visibility:hidden 一旦父節點元素應用了 visibility:hidden,則其子孫后代也都會全部不可見。不過存在隱藏“失效”的情況。當其子孫元素應用了 visibility:visible,那么這個子孫元素又會顯現出來。
不同樣式在消耗性能方面是不同的,譬如 box-shadow 從渲染角度來講十分耗性能,原因就是與其他樣式相比,它們的繪制代碼執行時間過長。這就是說,如果一個耗性能嚴重的樣式經常需要重繪,那么你就會遇到性能問題。其次你要知道,沒有不變的事情,在今天性能很差的樣式,可能明天就被優化,并且瀏覽器之間也存在差異。
因此關鍵在于,你要借助開發工具來分辨出性能瓶頸所在,然后設法減少瀏覽器的工作量。
好在 chrome 瀏覽器提供了許多強大的功能,讓我們可以檢測我們的動畫性能,除了上面提到的,我們還可以通過勾選下面這個 show FPS meter 顯示頁面的 FPS 信息,以及 GPU 的使用率:
官方文檔說[4],這是一個仍處于實驗階段的功能,所以在未來版本的瀏覽器中該功能的語法和行為可能隨之改變。
will-change 為 web 開發者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器可以在元素屬性真正發生變化之前提前做好對應的優化準備工作。 這種優化可以將一部分復雜的計算工作提前準備好,使頁面的反應更為快速靈敏。
看看 Can i Use - will-change[5],更新于 2021/03/31 :
使用方法示例:(具體每個取值的意義,去翻翻文檔)
will-change: auto will-change: scroll-position will-change: contents will-change: transform // Example of will-change: opacity // Example of will-change: left, top // Example of two will-change: unset will-change: initial will-change: inherit // 示例 .example{ will-change: transform; }
值得注意的是,用好這個屬性并不是很容易:
不要將 will-change 應用到太多元素上:瀏覽器已經盡力嘗試去優化一切可以優化的東西了。有一些更強力的優化,如果與 will-change 結合在一起的話,有可能會消耗很多機器資源,如果過度使用的話,可能導致頁面響應緩慢或者消耗非常多的資源。
有節制地使用:通常,當元素恢復到初始狀態時,瀏覽器會丟棄掉之前做的優化工作。但是如果直接在樣式表中顯式聲明了 will-change 屬性,則表示目標元素可能會經常變化,瀏覽器會將優化工作保存得比之前更久。所以最佳實踐是當元素變化之前和之后通過腳本來切換 will-change 的值。
不要過早應用 will-change 優化:如果你的頁面在性能方面沒什么問題,則不要添加 will-change 屬性來榨取一丁點的速度。will-change 的設計初衷是作為最后的優化手段,用來嘗試解決現有的性能問題。它不應該被用來預防性能問題。過度使用 will-change 會導致大量的內存占用,并會導致更復雜的渲染過程,因為瀏覽器會試圖準備可能存在的變化過程。這會導致更嚴重的性能問題。
給它足夠的工作時間:這個屬性是用來讓頁面開發者告知瀏覽器哪些屬性可能會變化的。然后瀏覽器可以選擇在變化發生前提前去做一些優化工作。所以給瀏覽器一點時間去真正做這些優化工作是非常重要的。使用時需要嘗試去找到一些方法提前一定時間獲知元素可能發生的變化,然后為它加上 will-change 屬性。
3D transform 會啟用GPU加速,例如 translate3D, scaleZ 之類,當然我們的頁面可能并沒有 3D 變換,但是不代表我們不能啟用 GPU 加速,在非 3D 變換的頁面也使用 3D transform 來操作,算是一種 hack 加速法。我們實際上不需要z軸的變化,但是還是假模假樣地聲明了,去欺騙瀏覽器。
參考文獻:
Rendering: repaint, reflow/relayout, restyle[6]
Scrolling Performance[7]
MDN--will-change[8]
How (not) to trigger a layout in WebKit[9]
High Performance Animations[10]
Accelerated Rendering in Chrome[11]
CSS3 制作3D旋轉球體[12]
“CSS3 3D行星運轉與瀏覽器渲染的原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。