本篇文章翻譯自adobe Web Platform Team的博客:CSS animations and transitions performance: looking inside the browser,雖然是一篇舊文,但是里面談到的知識點很有用,對CSS的性能優化有很大幫助。
在這篇文章中,我們會向你解釋瀏覽器是如何處理CSS Animation和CSS transition的,這樣你就可以不寫一行代碼就能憑借自己的直覺判斷一個動畫是否流暢,你就可以設計出更適合瀏覽器的、更絲般柔滑的用戶體驗。
一、瀏覽器的內部機制
讓我們撥開瀏覽器的頭紗看看它到底是如何工作的,一旦我們明白其內部機制,便能駕馭它了。
現代瀏覽器通常由兩個重要的線程組成,這兩個線程一起工作完成繪制頁面的任務:
- 主線程
- 合成線程
1、主線程需要做的任務如下:
- 運行Javascript
- 計算HTML元素的CSS樣式
- layout (relayout)
- 將頁面元素繪制成一張或多張位圖
- 將位圖發送給合成線程
2、合成線程主要任務是:
- 利用GPU將位圖繪制到屏幕上
- 讓主線程將可見的或即將可見的位圖發給自己
- 計算哪部分頁面是可見的
- 計算哪部分頁面是即將可見的(當你的滾動頁面的時候)
- 在你滾動時移動部分頁面
在很長的一段時間內,主線程都在忙於運行Javascript和繪制大型元素,當它忙碌的時候,它就沒空響應用戶的輸入了。
換個角度說,合成線程一直在嘗試保證對用戶輸入的響應。它會在頁面改變時每秒繪制60次頁面,即使頁面還不完整。
例如,當用戶滾動一個頁面時,合成線程會讓主線程提供最新的可見部分的頁面位圖,然而主線程不能及時的響應,這時合成線程不會等待,它會繪制已有的頁面位圖,對於沒有的部分則繪制白屏。
二、GPU
我之前提到了合成線程會使用GPU來繪制位圖,讓我們快速熟悉下GPU的概念。如今大多數手機、平板和電腦都帶有了GPU芯片,它非常的特別,它很擅長做某些事情,又很不擅長做其他事情。
GPU在做如下操作時很快:
- 繪制東西到屏幕上
- 一次次繪制同一張位圖到屏幕上
- 繪制同一張位圖到不同的位置、旋轉角度和縮放比例
GPU很不擅長做:
- 加載位圖到內存中
三、transition: height
現在我們對運行頁面的軟件和硬件都有了一個粗略的了解。讓我們來看看主線程和合成線程是如何處理CSS transition的。
假設我們將一個頁面元素的高度從100px漸變到200px,代碼如下:
div { height: 100px; transition: height 1s linear;
} div:hover { height: 200px;
}
下圖是一張主線程和合成線程的互相交互的時間線圖。注意:黃色盒子的操作是潛在耗時較長的,藍色盒子的操作是很快的。
你可以看到有很多黃色的盒子,這意味着瀏覽器要做很多復雜的操作,這就表明這個transition動畫很可能會卡。
在transition動畫的每一幀中,瀏覽器都要做下relayout和repaint,然后將位圖發送給GPU,之前我們提到了,加載位圖到GPU內存中是很慢的。
瀏覽器之所以這么拼命的工作是因為元素在不停的變化,而且修改元素的高度可能會導致子元素的大小也會變化,所以瀏覽器不得不進行relayout,在relayout之后主線程還需要重新生成元素的位圖。
四、transition: transform
所以高度的變化是很耗時的,有沒有什么東西耗時更少呢?
假設我們將一個元素縮小到其一半大小,同時假設我們使用了CSS transform屬性來縮放元素,那么這個縮小動畫的CSS如下:
div { transform: scale(0.5); transition: transform 1s linear;
} div:hover { transform: scale(1.0);
}
讓我們來看下這次的時間線圖:
這次我們很少看到黃色盒子了,這就意味着這個動畫可以很流暢!那么為什么transform的動畫會和height的動畫差別這么大呢?
依據規范,CSS transform屬性並不會觸發當前元素或附近元素的relayout,瀏覽器將當前元素視為一個整體,它會縮放、旋轉、移動這一整個元素。
這對瀏覽器來說是個天大的好消息!瀏覽器只需要在動畫開始之時生成位圖,然后將位圖發送給GPU,之后瀏覽器不需要做額外的relayout和repaint,甚至不需要發送位圖給GPU,瀏覽器只需要充分發揮GPU的長處:繪制同一張位圖到不同的位置、旋轉角度和縮放比例。
五、設計意圖
所以這是否意味着我們不應該使用height的動畫呢? 當然不是,有時這就是設計的需求,並且動畫也可以足夠快,可能你的元素是隔離的,並且不會導致其他部分的頁面觸發relayout;可能你的元素很簡單,瀏覽器可以很快完成repaint;更可能你的元素很小,瀏覽器只需要發送一張很小的位圖到GPU中。
當然,如果你可以在不影響設計意圖的情況下使用一個更低耗的CSS屬性自然是極好的。舉個例子:你設計了一個按鈕,在tap按鈕之后彈出一個菜單。彈出的過程是一個CSS動畫。按照一般思維,我們會用到CSS的top和height屬性來實現彈出效果。但其實我們可以用更低耗的CSS transform屬性來實現類似的彈出效果。
總結一下做動畫時速度很快的CSS屬性:
- CSS transform
- CSS opacity
- CSS filter (具體要看filter的復雜度)
這個列表目前很小,但是隨着瀏覽器越來越先進,你會看到這個列表越變越大。同樣的也不要小看這張列表上的屬性,你會驚訝居然可以用這么幾個簡單的屬性實現這么多復雜的動畫效果,發揮你們的創造力吧!