關於動畫到硬件加速的那點事兒
目前我們關於實現動畫的方式,無非就是類似 jQuery的動畫, CSS 的 animation,transition,tranform 或者 requestAnimationFrame 以及觸及GPU的硬件加速動畫。
jQuery的動畫不用說,就普通的 CSS 動畫而言使用浏覽器自身的渲染引擎來處理動畫的渲染,而觸發 CSS 硬件加速則是通過 GPU 來提升 CSS 動畫的性能,將有關於動畫的運算從 CPU 移植到 GPU 上,大大提高渲染動畫的效率。GPU 在對於渲染圖形方便的優勢,所以能在渲染動畫上更容易達到每秒60幀的順滑效果,使得視覺上順暢無比。
簡單來講,當一個頁面初次加載完畢的時候,浏覽器的渲染引擎便開始了它的工作:
1. 浏覽器解析HTML, 構建一棵 DOM 樹,其每個節點對應頁面的每個元素。
2. 渲染引擎對 DOM樹,以浏覽器解析後的 CSS ,會對應構建出一棵 Render 樹,其每個節點 RenderObject包含了所對應 DOM 的最終樣式信息。
3. 基於 Render 樹,渲染引擎會將其中的某些特定節點同時創建新的 RenderLayer,從而還會生成一棵 RenderLayer 樹。
4. 當 Render 樹和 RenderLayer 樹構建完畢之後,便依據兩者,遍歷各個節點開始渲染繪制出頁面。
再簡單介紹下 chrome 的 timeline 的幾個名詞:
Timer Fire: 定時器觸發,很簡單,比如 jquery 的動畫均是通過 setTimeout 來處理生產動畫效果(一般性的動畫),所以每次展現完畢後都會觸發下一個定時器事件 JS 的過程。
Recalculate Style:將 CSS 構建與 DOM 對應的的樹,一般稱之為 Render 樹的過程。
Layout:依據 Render 樹,以及 RenderLayer 樹進行頁面布局的過程。
Print:更具布局,繪制出頁面的過程
Composite Layers:渲染展現到屏幕的過程
實例頁面:
http://hello.exptui.com/demo/cssDemo/css-hardware.html
最古老的動畫形式:
HTML:
- <div class="square"></div>
CSS:
- .square{
- position:relative;
- top:50px;
- left: 50px;
- margin-bottom: 10px;
- height:80px;
- width:80px;
- background-color:#999;
- }
JS:
- $('.jquery').click(function(){
- $('.square').animate({
- height: 300,
- width: 300,
- left:200,
- top:200,
- opacity: 0.5},
- 3000, 'linear', function() {
- });
- });
以上是一個很簡單 jq 動畫效果,3秒鐘從原有圖形樣式轉變到指定的圖形樣式,但是,浏覽器所處理的卻是一段 jq 分割的復雜的過程,
每一次浏覽器都要處理包括 Timer Fire,Recalculate Style,Layout,Paint,Composite Layers,一次又一次的不斷重復又循環,內存占用率也是隨之增大。
如今的時代,css的動畫幫我們解決了很多難題,
普通的2D CSS動畫:
CSS:
- .css2d{
- width:300px;
- height:300px;
- left:200px;
- top:200px;
- transition: width 3s linear, height 3s linear,top 3s linear,left 3s linear;
- -webkit-transition: width 3s linear, height 3s linear,top 3s linear,left 3s linear,;
- }
JS:
- $('.css-2d').click(function(){
- $('.square').addClass('css2d');
- });
直接上圖,計算樣式以及重新布局均有浏覽器自身完成,並且少了定時器觸發事件這一塊,使得動畫運行方式更為自然,
還有內存圖:
或許感覺還不錯了,但是由於涉及到了一些或導致重排的屬性,使得動畫的時候一直重排重繪,那麼來看看硬件加速的樣式動畫吧,
吊炸天的硬件加速CSS動畫:
CSS:
- .hardware{
- -webkit-animation: hardware 2s infinite linear;
- animation: hardware 2s infinite linear;
- }
- @-webkit-keyframes hardware {
- 0% {
- transform: translate3d(0,0,0) scale3d(1,1,1);
- -webkit-transform: translate3d(0,0,0) scale3d(1,1,1);
- }
- 50% {
- transform: translate3d(150px,150px,0) scale3d(3,3,1);
- -webkit-transform: translate3d(150px,150px,0) scale3d(3,3,1);
- }
- }
- @keyframes hardware {
- 0% {
- transform: translate3d(0,0,0) scale3d(1,1,1);
- -webkit-transform: translate3d(0,0,0) scale3d(1,1,1);
- }
- 50% {
- transform: translate3d(150px,150px,0) scale3d(3,3,1);
- -webkit-transform: translate3d(150px,150px,0) scale3d(3,3,1);
- }
- }
JS:
- $('.css-hardware').click(function(){
- $('.square').addClass('hardware');
- });
對於硬件加速的動畫,浏覽器渲染的過程:
屌不屌其實都一樣啦,只不過沒有使用處理元素的一些,如height, width,left,top 等元素值的動畫,而使用了一些3D CSS屬性達到了我們所需的動畫效果,但是浏覽器處理動畫卻已經完全不一樣。動畫過程優化到了僅剩Recalculate Style,Composite Layers 兩個過程。
這邊為什麼會這麼吊呢,這就全依仗與 對應所生成的第三種樹 RenderLayer 樹,該元素節點被渲染引擎根據 CSS 創建了對應的 RenderLayer 樹節點,並將其綁定到 GPU,當該 RenderLayer 的 transform 等變化時候,跳過浏覽器的 Layout,以及 Print,直接有 GPU 對其做變換。這個也是直接提升動畫性能,內存突然降低(計算被移植到了GPU)的原因。
那麼如何來處理 CSS,將我們所需要進行動畫的元素構建時擁有新建的 RenderLayer 節點呢?
目前查詢到的資料來看,chrome 以下:
1. 3D或透視變換(perspective transform)CSS屬性
2. 使用加速視頻解碼的<video>節點
3. 擁有3D(WebGL)上下文或加速的2D上下文的<canvas>節點
4. 混合插件(如Flash)
5. 對自己的opacity做CSS動畫或使用一個動畫webkit變換的元素
6. 擁有加速CSS過濾器的元素
7. 元素有一個包含復合層的後代節點(一個元素擁有一個子元素,該子元素在自己的層裡)
8. 元素有一個z-index較低且包含一個復合層的兄弟元素(換句話說就是該元素在復合層上面渲染)
一般我們都會嘗試對需要進行動畫的元素添加一個 3D 的屬性從而來觸發其硬件加速的動畫效果,現在又有一個專門用作觸發生成新 RenderLayer 的屬性值 will-change。
當然以上動畫最優情況只正對於部分 GPU 加速處理的 CSS 屬性,對於涉及到重排 relayout 和 重繪 repaint 的屬性我們可能需要慎重考慮以及優化了。
重排:重排是頁面結構調整,獲取或者設置元素某些屬性值時,觸發了浏覽器 Layout 過程重新定義的情況,比如調整元素的幾何大小,位置,獲取寬高等。重排必然導致重繪,浏覽器渲染總會依照 Layout,Paint,Composite Layers 的流程來處理。下面是觸發重排的一些主要情況:
1. 腳本操作DOM,增加刪除可視的DOM節點
2. 操作 class 屬性,或者設置 style 屬性
觸發重排的屬性:
盒子模型的相關屬性:width, height, padding, margin, display, border
定位特性的相關屬性:top, bottom, left, right, position, float, clear
內容的相關屬性:text-align, overflow, font-family, line-height,font-size
3. 計算獲取當前 offsetTop/Left/Width/Height, scrollTop/Left/Width/Height, clientTop/Left/Width/Height, width, height 等屬性值
4. 內容變化, 窗口調整等
重繪:重排必然導致重繪,並且對於僅調整各類屬性色值等時,浏覽器重新繪制輸出到浏覽器的情況。
觸發重排的屬性:color, border-style, visibility, background, outline, box-shadow 等
一些動畫的重排重繪我們不可避免,我們要做的並不是禁止它,存在即合理,事實只有我們濫用。以下是一些簡單的優化重排,重繪的方法:
1. 將多次需要頻繁改變的樣式一次性完成,不要頻繁的獲取計算樣式。
2. 將大量的操作 DOM 操作放置到頁面之外進行。
3. 盡可能在最末端需要調整的 DOM 上改變 class,減小對其它 DOM 的影響。
4. 動畫盡量應用到 position 為 absolute 或者 fixed 的元素上,以便當前元素的動畫造成大規模的重排。