Web動畫形式
首先我們來了解一下Web有哪些動畫形式
1. CSS3動畫 Transform(變形) Transition(過渡) Animation(動畫) 2. JS動畫(操作DOM、修改CSS屬性值) 3. Canvas動畫 4. SVG動畫 5. 以Three.js為首的3D動畫
以上各種動畫形式都可以制作出一種類型的動畫,那就是幀動畫,也叫序列幀動畫,定格動畫,逐幀動畫等,這里我們統一用幀動畫來表述。
應用場景
幀動畫一般用來實現稍微復雜一點的動畫效果,同時希望動畫更細膩,設計師更自由的發揮。他可以定義到每一個時間刻度上的展現內容,我們一般用幀動畫來做頁面的Loading,小人物,小物體元素的簡單動畫。我們想象中的幀動畫應該有以下幾個特點:
- 可以自由控制播放、暫停和停止
- 可以控制播放次數,播放速度
- 可以添加交互,在播放完成后添加事件
- 瀏覽器兼容性好
素材准備
幀動畫的素材一般是先由設計師在PS中的時間軸上設計好了,然后導出圖片給前端人員,PS制作時間軸動畫一般是用來制作稍微簡單的動畫,操作簡單,方便。
或者是由設計師在AE的時間軸進行設計,因為AE內置了更豐富的動作效果,比如轉換,翻轉之類的,AE可以幫助我們實現更復雜的效果,然后再導出圖片給前端人員。
這里幀動畫素材的要求,每一幀的圖片最好是偶數寬高,偶數張,最好周圍能有一些留白。
實現方案
將目前想到的解決方案梳理如下圖,同時我們將對每種方案進行詳細介紹。
一、GIF圖
我們可以將上面制作的幀動畫導出成GIF圖,GIF圖會連續播放,無法暫停,它往往用來實現小細節動畫,成本較低、使用方便。但其缺點也是很明顯的:
- 畫質上,gif 支持顏色少(最大256色)、Alpha 透明度支持差,圖像鋸齒毛邊比較嚴重;
- 交互上,不能直接控制播放、暫停、播放次數,靈活性差;
- 性能上,gif 會引起頁面周期性的繪畫,性能較差。
二、CSS3幀動畫
CSS3幀動畫是我們今天需要重點介紹的方案,最核心的是利用CSS3中Animation動畫,確切的說是使用animation-timing-function
的階梯函數 steps(number_of_steps, direction)
來實現逐幀動畫的連續播放。
幀動畫的實現原理是不斷切換視覺內圖片內容,利用視覺滯留生理現象來實現連續播放的動畫效果,下面我們來介紹制作CSS3幀動畫的幾種方案。
(1)連續切換動畫圖片地址src(不推薦)
我們將圖片放到元素的背景中(background-image
),通過更改 background-image
的值實現幀的切換。但是這種方式會有以下幾個缺點,所以該方案不推薦。
- 多張圖片會帶來多個 HTTP 請求
- 每張圖片首次加載會造成圖片切換時的閃爍
- 不利於文件的管理
(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() 函數:
- steps 函數指定了一個階躍函數,它接受兩個參數。
- 第一個參數接受一個整數值,表示兩個關鍵幀之間分幾步完成。
- 第二個參數有兩個值< start > or < end >。默認值為< end > 。
- step-start 等同於 step(1, start)。step-end 等同於 step(1, end)。
綜上我們可以知道,因為我們詳細定義了一個關鍵幀周期,從開始到結束,每兩個關鍵幀之間分 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);} }
三、JS幀動畫
(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
如上圖,我們通過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 |
通過分析以上數據我們可以得出以下幾點:
- 除了css
transform:translate3d()
方案,其他方案的FPS都能達到60FPS的流暢程度,但該方案的FPS也不是很低。 - CPU占用率最低的方案是 css
transform:translate3d()
方案。 - GPU占用最低的方案是 JS Canvas 繪制方案。
- CSS 方案沒有腳本開銷
- Rendering 最少的是 css
transform:translate3d()
方案。 - Painting 最少的是 css
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()
方案在實現和性能上都明顯優於其他方案。
轉自:https://blog.csdn.net/u013778905/article/details/80773003
參照:https://aotu.io/notes/2016/05/17/css3-animation-frame/index.html