做者: 前端向朔 from 迅雷前端css
原文地址:幀動畫的多種實現方式與性能對比html
本文目錄前端
Web動畫形式
應用場景
素材準備
實現方案
1、GIF圖
2、CSS3幀動畫
3、JS幀動畫
方案總結
注意事項
總結
複製代碼
首先咱們來了解一下Web有哪些動畫形式css3
1. CSS3動畫
Transform(變形)
Transition(過渡)
Animation(動畫)
2. JS動畫(操做DOM、修改CSS屬性值)
3. Canvas動畫
4. SVG動畫
5. 以Three.js爲首的3D動畫
複製代碼
以上各類動畫形式均可以製做出一種類型的動畫,那就是幀動畫,也叫序列幀動畫,定格動畫,逐幀動畫等,這裏咱們統一用幀動畫來表述。canvas
幀動畫通常用來實現稍微複雜一點的動畫效果,同時但願動畫更細膩,設計師更自由的發揮。他能夠定義到每個時間刻度上的展示內容,咱們通常用幀動畫來作頁面的Loading,小人物,小物體元素的簡單動畫。咱們想象中的幀動畫應該有如下幾個特色:瀏覽器
幀動畫的素材通常是先由設計師在PS中的時間軸上設計好了,而後導出圖片給前端人員,PS製做時間軸動畫通常是用來製做稍微簡單的動畫,操做簡單,方便。函數
或者是由設計師在AE的時間軸進行設計,由於AE內置了更豐富的動做效果,好比轉換,翻轉之類的,AE能夠幫助咱們實現更復雜的效果,而後再導出圖片給前端人員。工具
這裏幀動畫素材的要求,每一幀的圖片最好是偶數寬高,偶數張,最好周圍能有一些留白。性能
將目前想到的解決方案梳理以下圖,同時咱們將對每種方案進行詳細介紹。 測試
咱們能夠將上面製做的幀動畫導出成GIF圖,GIF圖會連續播放,沒法暫停,它每每用來實現小細節動畫,成本較低、使用方便。但其缺點也是很明顯的:
CSS3幀動畫是咱們今天須要重點介紹的方案,最核心的是利用CSS3中Animation動畫,確切的說是使用animation-timing-function
的階梯函數 steps(number_of_steps, direction)
來實現逐幀動畫的連續播放。
幀動畫的實現原理是不斷切換視覺內圖片內容,利用視覺滯留生理現象來實現連續播放的動畫效果,下面咱們來介紹製做CSS3幀動畫的幾種方案。
(1)連續切換動畫圖片地址src(不推薦)
咱們將圖片放到元素的背景中(background-image
),經過更改 background-image
的值實現幀的切換。可是這種方式會有如下幾個缺點,因此該方案不推薦。
(2)連續切換雪碧圖位置(推薦)
咱們將全部的幀動畫圖片合併成一張雪碧圖,經過改變 background-position
的值來實現動畫幀切換。分兩步進行:
步驟一: 將動畫幀合併爲雪碧圖,雪碧圖的要求能夠看上面素材準備,好比下面這張幀動畫雪碧圖,共20幀。
步驟二: 使用steps階梯函數切換雪碧圖位置
先看寫法一:
<div class="sprite"></div>
.sprite {
width: 300px;
height: 300px;
background-repeat: no-repeat;
background-image: url(frame.png);
animation: frame 333ms steps(1,end) both infinite;
}
@keyframes frame {
0% {background-position: 0 0;}
5% {background-position: -300px 0;}
10% {background-position: -600px 0;}
15% {background-position: -900px 0;}
20% {background-position: -1200px 0;}
25% {background-position: -1500px 0;}
30% {background-position: -1800px 0;}
35% {background-position: -2100px 0;}
40% {background-position: -2400px 0;}
45% {background-position: -2700px 0;}
50% {background-position: -3000px 0;}
55% {background-position: -3300px 0;}
60% {background-position: -3600px 0;}
65% {background-position: -3900px 0;}
70% {background-position: -4200px 0;}
75% {background-position: -4500px 0;}
80% {background-position: -4800px 0;}
85% {background-position: -5100px 0;}
90% {background-position: -5400px 0;}
95% {background-position: -5700px 0;}
100% {background-position: -6000px 0;}
}
複製代碼
針對以上動畫有疑問?
問題一:既然都詳細定義關鍵幀了,是否是能夠不用steps函數了,直接定義linear變化不就行了嗎?
animation: frame 10s linear both infinite;
若是咱們定義成這樣,動畫是不會階梯狀,一步一步執行的,而是會連續的變化背景圖位置,是移動的效果,而不是切換的效果,以下圖:
問題二:不是應該設置爲20步嗎,怎麼變成了1?
這裏咱們先來了解下animation-timing-function
屬性。
CSS animation-timing-function
屬性定義CSS動畫在每一動畫週期中執行的節奏。對於關鍵幀動畫來講,timing function做用於一個關鍵幀週期而非整個動畫週期,即從關鍵幀開始開始,到關鍵幀結束結束。
timing-function 做用於每兩個關鍵幀之間,而不是整個動畫。
接着咱們來了解下steps() 函數:
綜上咱們能夠知道,由於咱們詳細定義了一個關鍵幀週期,從開始到結束,每兩個關鍵幀之間分 1 步展現完,也就是說0% ~ 5%之間變化一次,5% ~ 10%變化一次,因此咱們這樣寫才能達到想要的效果。
再看寫法二:
<div class="sprite"></div>
.sprite {
width: 300px;
height: 300px;
background-repeat: no-repeat;
background-image: url(frame.png);
animation: frame 333ms steps(20) both infinite;
}
@keyframes frame {
0% {background-position: 0 0;}//可省略
100% {background-position: -6000px 0;}
}
複製代碼
這裏咱們定義了關鍵幀的開始和結束,也就是定義了一個關鍵幀週期,但由於咱們沒有詳細的定義每一幀的展現,因此咱們要將0%~100%這個區間分紅20步來階段性展現。
也能夠換成關鍵字的寫法,還能夠只定義最後一幀,由於默認第一幀就是初始位置。
@keyframes frame {
from {background-position: 0 0;}//可省略
to {background-position: -6000px 0;}
}
複製代碼
(3)連續移動雪碧圖位置(移動端推薦)
跟第二種基本一致,只是切換雪碧圖的位置過程換成了transform:translate3d()
來實現,不過要加多一層overflow: hidden;
的容器包裹,這裏咱們以只定義初始和結束幀爲例,使用transform能夠開啓GPU加速,提升機器渲染效果,還能有效解決移動端幀動畫抖動的問題。
<div class="sprite-wp">
<div class="sprite"></div>
</div>
.sprite-wp {
width: 300px;
height: 300px;
overflow: hidden;
}
.sprite {
width: 6000px;
height: 300px;
will-change: transform;
background: url(frame.png) no-repeat center;
animation: frame 333ms steps(20) both infinite;
}
@keyframes frame {
0% {transform: translate3d(0,0,0);}
100% {transform: translate3d(-6000px,0,0);}
}
複製代碼
(1)經過JS來控制img的src屬性切換(不推薦)
和上面CSS3幀動畫裏面切換元素background-image
屬性同樣,會存在多個請求等問題,因此該方案咱們不推薦,可是這是一種解決思路。
(2)經過JS來控制Canvas圖像繪製
經過Canvas製做幀動畫的原理是用drawImage方法將圖片繪製到Canvas上,不斷擦除和重繪就能獲得咱們想要的效果。
<canvas id="canvas" width="300" height="300"></canvas>
(function () {
var timer = null,
canvas = document.getElementById("canvas"),
context = canvas.getContext('2d'),
img = new Image(),
width = 300,
height = 300,
k = 20,
i = 0;
img.src = "frame.png";
function drawImg() {
context.clearRect(0, 0, width, height);
i++;
if (i == k) {
i = 0;
}
context.drawImage(img, i * width, 0, width, height, 0, 0, width, height);
window.requestAnimationFrame(drawImg);
}
img.onload = function () {
window.requestAnimationFrame(drawImg);
}
})();
複製代碼
上面是經過改變裁剪圖像的X座標位置來實現動畫效果的,也能夠經過改變畫布上放置圖像的座標位置實現,以下: context.drawImage(img, 0, 0, width*k, height,-i*width,0,width*k,height);
。
(3)經過JS來控制CSS屬性值變化
這種方式和前面CSS3幀動畫同樣,有三種方式,一種是經過JS切換元素背景圖片地址background-image
,一種是經過JS切換元素背景圖片定位background-position
,最後一種是經過JS移動元素transform:translate3d()
,第一種不作介紹,由於一樣會存在多個請求等問題,不推薦使用,這裏實現後面兩種。
background-position
.sprite {
width: 300px;
height: 300px;
background: url(frame.png) no-repeat 0 0;
}
<div class="sprite" id="sprite"></div>
(function(){
var sprite = document.getElementById("sprite"),
picWidth = 300,
k = 20,
i = 0,
timer = null;
// 重置背景圖片位置
sprite.style = "background-position: 0 0";
// 改變背景圖位置
function changePosition(){
sprite.style = "background-position: "+(-picWidth*i)+"px 0";
i++;
if(i == k){
i = 0;
}
window.requestAnimationFrame(changePosition);
}
window.requestAnimationFrame(changePosition);
})();
複製代碼
transform:translate3d()
.sprite-wp {
width: 300px;
height: 300px;
overflow: hidden;
}
.sprite {
width: 6000px;
height: 300px;
will-change: transform;
background: url(frame.png) no-repeat center;
}
<div class="sprite-wp">
<div class="sprite" id="sprite"></div>
</div>
(function () {
var sprite = document.getElementById("sprite"),
picWidth = 300,
k = 20,
i = 0,
timer = null;
// 重置背景圖片位置
sprite.style = "transform: translate3d(0,0,0)";
// 改變背景圖移動
function changePosition() {
sprite.style = "transform: translate3d(" + (-picWidth * i) + "px,0,0)";
i++;
if (i == k) {
i = 0;
}
window.requestAnimationFrame(changePosition);
}
window.requestAnimationFrame(changePosition);
})();
複製代碼
總結以上幾種方案,咱們能夠看到GIF圖有必定的優勢同時缺點和侷限性也比較明顯,因此這種方案看狀況選擇使用。
其餘實現方案的性能如何呢,咱們來比較一下,若是測試結果出現誤差,可能與測試環境變化有關。
測試環境:
系統:Windows 10 專業版
處理器:Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz 3.41GHz
RAM: 8.00GB
瀏覽器:Chrome 72.0
複製代碼
CSS transform:translate3d()
方案性能數據
如上圖,咱們經過Chrome瀏覽器的各類工具,查看了每種方案的 FPS、CPU佔用率、GPU佔用、Scripting、Rendering、Painting、內存的使用狀況,獲得如下數據:
性能-方案 | cssbackground-position |
csstransform:translate3d() |
JS Canvas | JSbackground-position |
JStransform:translate3d() |
---|---|---|---|---|---|
FPS | 60 | 51 | 60 | 60 | 60 |
CPU | 5%-6.2% | 0.3%-1% | 7%-8% | 6%-8% | 6%-8% |
GPU | 3.8MB | 4-10MB | 0 | 3.8MB | 4-11MB |
Scripting | 0 | 0 | 2.51% | 2.61% | 3.18% |
Rendering | 1.17% | 0.141% | 0.84% | 1.65% | 2.71% |
Painting | 1.58% | 0.01% | 1.63% | 1.75% | 1.05% |
內存 | 20112K | 21120K | 21588K | 20756K | 21576K |
經過分析以上數據咱們能夠得出如下幾點:
transform:translate3d()
方案,其餘方案的FPS都能達到60FPS的流暢程度,但該方案的FPS也不是很低。transform:translate3d()
方案。transform:translate3d()
方案。transform:translate3d()
方案。結論:咱們看到,在7個指標中,css transform:translate3d()
方案將其中的4個指標作到了最低,從這點看,咱們徹底有理由選擇這種方案來實現CSS幀動畫。
至於其餘方案的絕對比較暫時無法給出結論,看具體狀況來選擇,也看開發者對哪一個性能指標的追求。
延伸來看咱們的Web動畫,每種形式的動畫都有其各自的有點,好比大量的粒子效果用Canvas繪製方案確定要比DOM+CSS實現要好的,大量的CSS屬性值變換,使用 transform
實現性能是要更好的。
素材:動畫圖片寬高最好是偶數,總幀數最好是偶數,圖片拼接處最好有必定的留白。
適配:移動端適配最好不用rem,由於rem的計算會形成小數四捨五入,形成必定的抖動效果,建議直接用px做爲單位,同時輔助以scale(zoom)媒體查詢進行適配。若是使用rem適配,試試使用transform的方案,抖動問題能夠獲得優化解決。
對於幀與幀之間的盈虧互補現象致使動畫抖動,想要了解更多,能夠閱讀《CSS技巧:逐幀動畫抖動解決方案》。
tips:使用 will-change
能夠在元素屬性真正發生變化以前提早作好對應準備。
本文咱們主要梳理了目前實現幀動畫的幾種方案,同時對各類方案進行效果實現,優劣討論,性能對比,同時簡單介紹了幀動畫實現過程的注意事項,最後咱們得出結論,css transform:translate3d()
方案在實現和性能上都明顯優於其餘方案。
參考來源: