前端動效講解與實戰(zhàn)

作者:vivo 互聯(lián)網(wǎng)前端團(tuán)隊- ZhaoJie

本文將從各個角度來對動畫整個體系進(jìn)行分類,并且介紹各種前端動畫的實現(xiàn)方法,最后我們將總結(jié)在實際開發(fā)中的各個場景的動畫選擇方案。

一、背景

前端動畫場景需求多

對眾多動畫場景的技術(shù)實現(xiàn)方案選擇上比較模糊

各動畫方案的優(yōu)劣及適用場景認(rèn)識模糊

現(xiàn)有動畫庫太多,不知道選哪個

主流動畫庫的適用場景認(rèn)識模糊

下面首先讓我們從各個角度來對動畫整個體系進(jìn)行分類,讓我們清晰的了解動畫整個體系。

二、分類

2.1 用途角度

首先我們從動畫的用途或者說是業(yè)務(wù)的角度來進(jìn)行區(qū)分,將我們平時的動畫分為展示型動畫和交互型動畫。

2.1.1 展示型動畫

類似于一張GIF圖,或者一段視頻。比如在開啟寶箱的時候,我們會加入一個切場過渡動畫,來替代原有的生硬等待結(jié)果。

展示型動畫在實際使用的場景中,實現(xiàn)的方法很多,比如用GIF圖,canvas,CSS3動畫等,但是最終輸出的結(jié)果是不帶有交互的,也就是從動畫起始狀態(tài)到結(jié)束狀態(tài)一氣呵成,這個過程用戶可以感知,但是無法參與

2.1.2 交互型動畫

用戶自已參與的,對于交互性動畫而言,我們可以在動畫播放的某個時間節(jié)點觸發(fā)相應(yīng)的操作,進(jìn)而讓用戶參與到其中,最常見的例子紅包雨,不僅僅能提升用戶的體驗,還能提升我們的產(chǎn)品的多元性。

然而交互性動畫經(jīng)常面臨的一個問題就是,通過原生代碼實現(xiàn)交互動畫是很復(fù)雜的,同時性能和兼容性是不得不認(rèn)真考慮的問題,比較好的解決方案還是尋求相關(guān)的框架。

2.2 繪制技術(shù)角度

不管采用什么方式來制作動畫,最終呈現(xiàn)到前端頁面的無非是以下三種形式:

  1. Canvas
  2. div
  3. SVG

PS:為了簡單也可以用視頻,但除非動畫的播放場景固定,不然移動端視頻在不同app、不同機(jī)型、不同系統(tǒng)的播放顯示都不太一樣,容易踩不少坑。

2.2.1 不同繪制技術(shù)的性能差異

Canvas

  • 效率高、性能好、可控性高,只能處理位圖,內(nèi)存占用恒定

  • 依賴分辨率

  • 不支持事件處理器

  • 弱的文本渲染能力

  • 能夠以 .png 或 .jpg 格式保存結(jié)果圖像

  • 最適合圖像密集型的游戲,其中的許多對象會被頻繁重繪

div

  • 包括CSS控制的DOM動畫、JS控制的DOM動畫

  • 比較適合簡單的數(shù)量較少的復(fù)雜度較低的動畫

SVG

  • 處理矢量圖,不失真

  • 不依賴分辨率

  • 支持事件處理器

  • 最適合帶有大型渲染區(qū)域的應(yīng)用程序(比如谷歌地圖)

  • 復(fù)雜度高會減慢渲染速度(任何過度使用 DOM 的應(yīng)用都不快)

  • 不適合游戲應(yīng)用

2.2.2 Canvas和SVG比較

一句話總結(jié):都是2D做圖,svg是矢量圖,canvas是位圖。canvas 是逐像素進(jìn)行渲染的,適合游戲。

SVG

  • SVG繪制的是矢量圖,縮放不影響顯示,所以最適合帶有大型渲染區(qū)域的應(yīng)用程序(比如谷歌地圖)

  • SVG 是一種使用 XML 描述 2D 圖形的語言。

  • SVG 基于 XML,這意味著 SVG DOM 中的每個元素都是可用的。您可以為某個元素附加 JavaScript 事件處理器。

  • 在 SVG 中,每個被繪制的圖形均被視為對象。如果 SVG 對象的屬性發(fā)生變化,那么瀏覽器能夠自動重現(xiàn)圖形。

Canvas

  • Canvas 通過 JavaScript 來繪制 2D 圖形。

  • Canvas 是逐像素進(jìn)行渲染的。

  • 在 Canvas 中,一旦圖形被繪制完成,它就不會繼續(xù)得到瀏覽器的關(guān)注。如果其位置發(fā)生變化,那么整個場景也需要重新繪制,包括任何或許已被圖形覆蓋的對象。

  • Canvas只占用一個DOM節(jié)點,在做一些煙花、飄雪等運(yùn)動元素很多的動畫時,會比CSS/SVG性能好。

性能比較

  • 一般情況下,隨著屏幕大小的增大,canvas將開始降級,因為需要繪制更多的像素。

  • 隨著屏幕上的對象數(shù)目增多,SVG 將開始降級,因為我們正不斷將這些對象添加到 DOM 中。

  • 這些度量不一定準(zhǔn)確,以下方面的不同一定會引起變化:實現(xiàn)和平臺、是否使用完全硬件加速的圖形,以及 JavaScript 引擎的速度。

2.3 動畫類型角度

前端動效開發(fā),首先應(yīng)該確定的是

動畫用途->確認(rèn)動畫類型->確認(rèn)繪制技術(shù)->確認(rèn)動畫的實現(xiàn)方式。

雖然最終呈現(xiàn)動畫的載體(繪制技術(shù))就三種,但實現(xiàn)動畫的方式卻很多,得從動畫類型出發(fā)討論動畫的實現(xiàn)方式:

(1)逐幀動畫(序列幀動畫)

  • GIF實現(xiàn)

  • CSS實現(xiàn)(animation)

  • JS+DOM實現(xiàn)

  • JS+canvas實現(xiàn)

(2)補(bǔ)間動畫(Tween動畫\關(guān)鍵幀動畫)

  • CSS實現(xiàn)(transition、animation等)使用一些緩動函數(shù)

  • JS實現(xiàn)

(3)SVG動畫

  • 使用 XML 格式定義圖形

  • 可以用AI等SVG編輯工具生成SVG圖片后,配合anime.js、GSAP等現(xiàn)有庫進(jìn)行動畫制作

(4)骨骼動畫

  • 一般采用Spine、DragonBones等工具導(dǎo)出相應(yīng)資源圖片和JSON動畫配置資源后使用。

(5)3D動畫

  • DOM操作用CSS 3D實現(xiàn)。(perspective屬性、css3d-engine

  • 場景搭建用webGL(Three.js等)

  • 3D模型動畫用Blender或maya等制作完成后導(dǎo)出使用

2.3.1 逐幀動畫(序列幀動畫)

逐幀動畫是在時間幀上逐幀繪制幀內(nèi)容,由于是一幀一幀的畫,所以逐幀動畫具有非常大的靈活性,幾乎可以表現(xiàn)任何想表現(xiàn)的內(nèi)容。

由于逐幀動畫的幀序列內(nèi)容不一樣,不僅增加制作負(fù)擔(dān)而且最終輸出的文件量也很大,但它的優(yōu)勢也很明顯:因為它相似與電影播放模式,很適合于表演很細(xì)膩的動畫,如3D效果、人物或動物急劇轉(zhuǎn)身等等效果。

所以逐幀動畫的實現(xiàn)核心是什么,就是將我們的這些靜態(tài)的圖片進(jìn)行快速的循環(huán)播放,形成了一個動態(tài)的動畫效果。這就是幀動畫。

2.3.1.1 GIF實現(xiàn)

我們可以將幀動畫導(dǎo)出成GIF圖,GIF圖會連續(xù)播放,無法暫停,它往往用來實現(xiàn)小細(xì)節(jié)動畫,成本較低、使用方便。但其缺點也是很明顯的:

  1. 畫質(zhì)上,GIF 支持顏色少(最大256色)、Alpha 透明度支持差,圖像鋸齒毛邊比較嚴(yán)重;

  2. 交互上,不能直接控制播放、暫停、播放次數(shù),靈活性差;

  3. 性能上,GIF 會引起頁面周期性的繪畫,性能較差。

2.3.1.2 CSS實現(xiàn)

CSS3幀動畫是我們今天需要重點介紹的方案,最核心的是利用CSS3中Animation動畫,確切的說是使用animation-timing-function 的階梯函數(shù) steps(number_of_steps, direction) 來實現(xiàn)逐幀動畫的連續(xù)播放。

幀動畫的實現(xiàn)原理是不斷切換視覺內(nèi)圖片內(nèi)容,利用視覺滯留生理現(xiàn)象來實現(xiàn)連續(xù)播放的動畫效果,下面我們來介紹制作CSS3幀動畫的幾種方案。

(1)連續(xù)切換動畫圖片地址src(不推薦)

我們將圖片放到元素的背景中(background-image),通過更改 background-image 的值實現(xiàn)幀的切換。但是這種方式會有以下幾個缺點,所以該方案不推薦。

  • 多張圖片會帶來多個 HTTP 請求

  • 每張圖片首次加載會造成圖片切換時的閃爍

  • 不利于文件的管理

(2)連續(xù)切換雪碧圖位置(推薦)我們將所有的幀動畫圖片合并成一張雪碧圖,通過改變 background-position 的值來實現(xiàn)動畫幀切換。分兩步進(jìn)行:

步驟一:

將動畫幀合并為雪碧圖,雪碧圖的要求可以看上面素材準(zhǔn)備,比如下面這張幀動畫雪碧圖,共20幀。

(圖片來源于:幀動畫的多種實現(xiàn)方式與性能對比)

步驟二:

使用steps階梯函數(shù)切換雪碧圖位置

寫法一:

<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;}
}

針對以上動畫有疑問?

問題一:既然都詳細(xì)定義關(guān)鍵幀了,是不是可以不用steps函數(shù)了,直接定義linear變化不就好了嗎?

animation: frame 10s linear both infinite;

如果我們定義成這樣,動畫是不會階梯狀,一步一步執(zhí)行的,而是會連續(xù)的變化背景圖位置,是移動的效果,而不是切換的效果,如下圖:

問題二: 不是應(yīng)該設(shè)置為20步嗎,怎么變成了1?

這里我們先來了解下animation-timing-function屬性。CSS animation-timing-function屬性定義CSS動畫在每一動畫周期中執(zhí)行的節(jié)奏。

綜上我們可以知道,因為我們詳細(xì)定義了一個動畫周期,也就是說0% ~ 5%之間變化一次,5% ~ 10%變化一次,所以我們這樣寫才能達(dá)到想要的效果。

寫法二:

<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;}
}

這里我們定義了關(guān)鍵幀的開始和結(jié)束,也就是定義了一個關(guān)鍵幀周期,但因為我們沒有詳細(xì)的定義每一幀的展示,所以我們要將0%~100%這個區(qū)間分成20步來階段性展示。

(3)連續(xù)移動雪碧圖位置(移動端推薦)

跟第二種基本一致,只是切換雪碧圖的位置過程換成了transform:translate3d()來實現(xiàn),不過要加多一層overflow: hidden;的容器包裹,這里我們以只定義初始和結(jié)束幀為例,使用transform可以開啟GPU加速,提高機(jī)器渲染效果,還能有效解決移動端幀動畫抖動的問題。

<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);}
}

steps() 函數(shù)詳解

從上面的代碼我們可以發(fā)現(xiàn),CSS實現(xiàn)的核心就是使用animation-timing-function緩動函數(shù)的階梯函數(shù)steps(number_of_steps, direction)來實現(xiàn)逐幀動畫的連續(xù)播放的。

接著我們來了解下steps() 函數(shù):

steps 指定了一個階梯函數(shù),包含兩個參數(shù):

  • 第一個參數(shù)指定了函數(shù)中的間隔數(shù)量(必須是正整數(shù));

  • 第二個參數(shù)可選,指定在每個間隔的起點或是終點發(fā)生階躍變化,接受 start 和 end 兩個值,默認(rèn)為 end。

  • start 第一幀是第一步動畫的結(jié)束,end 第一幀是第一步動畫的開始。

除了 steps 函數(shù),animation-timing-function 還有兩個與逐幀動畫相關(guān)的屬性值 step-start 與 step-end:

  • step-start 等同于 steps(1,start)

  • step-end 等同于 steps(1,end)

2.3.1.3 JS實現(xiàn)

(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坐標(biāo)位置來實現(xiàn)動畫效果的,也可以通過改變畫布上放置圖像的坐標(biāo)位置實現(xiàn),如下:

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(),第一種不做介紹,因為同樣會存在多個請求等問題,不推薦使用,這里實現(xiàn)后面兩種。

切換元素背景圖片位置 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);
})();

2.3.1.4 性能分析

我們通過Chrome瀏覽器的各種工具,查看了每種方案的 FPS、CPU占用率、GPU占用、Scripting、Rendering、Painting、內(nèi)存的使用情況,得到以下數(shù)據(jù):

通過分析以上數(shù)據(jù)我們可以得出以下幾點:

  1. 除了CSS transform:translate3d() 方案,其他方案的FPS都能達(dá)到60FPS的流暢程度,但該方案的FPS 也不是很低。

  2. CPU占用率最低的方案是CSS transform:translate3d() 方案。

  3. GPU占用最低的方案是JS canvas 繪制方案。

  4. CSS 方案沒有腳本開銷。

  5. Rendering 最少的是CSS transform:translate3d() 方案。

  6. Painting 最少的是CSS transform:translate3d() 方案。

  7. 各方案內(nèi)存占用區(qū)別不大。

結(jié)論:我們看到,在7個指標(biāo)中,CSS transform:translate3d() 方案將其中的4個指標(biāo)做到了最低,從這點看,我們完全有理由選擇這種方案來實現(xiàn)CSS幀動畫。

2.3.2 補(bǔ)間動畫(Tween動畫\關(guān)鍵幀動畫)

補(bǔ)間動畫是動畫的基礎(chǔ)形式之一,又叫做中間幀動畫,漸變動畫,指的是人為設(shè)定動畫的關(guān)鍵狀態(tài),也就是關(guān)鍵幀,而關(guān)鍵幀之間的過渡過程只需要由計算機(jī)處理渲染的一種動畫形式。

說白了,就是我們在做動畫的時候,只需要指定幾個特殊時刻動畫的狀態(tài),其余的狀態(tài)由計算機(jī)自動計算補(bǔ)充。

實現(xiàn)補(bǔ)間動畫常見的手段主要由以下幾種:

  • CSS3 Animation:通過animation(除steps()以外的時間函數(shù))屬性在每個關(guān)鍵幀之間插入補(bǔ)間動畫。

  • CSS3 Transition:區(qū)別于animation,transition只能設(shè)定初始和結(jié)束時刻的兩個關(guān)鍵幀狀態(tài)。

  • 利用JavaScript實現(xiàn)動畫:例如JavaScript動畫庫或框架,Anime.js 或者TweenJS,它是CreateJS的其中一個套件。另外,在Flash業(yè)界久負(fù)盛名的GreenSock推出的GSAP(GreenSock Animation Platform)也新引入了對Javascript動畫的支持。

2.3.2.1 CSS實現(xiàn)

(1)transition 動畫

transition允許CSS的屬性值在一定的時間區(qū)間內(nèi)平滑地過渡,即指定元素的初始狀態(tài) 和末尾狀態(tài),既可以完成一個動畫,中間的變化完全有瀏覽器自己決定。動畫的效果主要還是看transition相關(guān)屬性即可。

然而利用transition制作的動畫也有著顯著的缺點:

  1. transition需要事件觸發(fā),所以沒法在網(wǎng)頁加載時自動發(fā)生。

  2. transition是一次性的,不能重復(fù)發(fā)生,除非一再觸發(fā)。

  3. transition只能定義開始狀態(tài)和結(jié)束狀態(tài),不能定義中間狀態(tài),也就是說只有兩個狀態(tài)。

  4. 一條transition規(guī)則,只能定義一個屬性的變化,不能涉及多個屬性。

(2)animation 動畫

利用animation可以完成一個完整的CSS補(bǔ)間動畫,如上面所說,我們只需要定義幾個特殊時刻的動畫狀態(tài)即可。這個特殊時刻通常我們叫做關(guān)鍵幀。

keyframes 關(guān)鍵幀

Keyframes具有其自己的語法規(guī)則,他的命名是由"@keyframes"開頭,后面緊接著是這個“動畫的名稱”加上一對花括號“{}”,括號中就是一些不同時間段樣式規(guī)則,有點像我們CSS的樣式寫法一樣。

對于一個"@keyframes"中的樣式規(guī)則是由多個百分比構(gòu)成的,如“0%”到"100%"之間,我們可以在這個規(guī)則中創(chuàng)建多個百分比,我們分別給每一個百分比中給需要有動畫效果的元素加上不同的屬性,從而讓元素達(dá)到一種在不斷變化的效果,比如說移動,改變元素顏色,位置,大小,形狀等。

不過有一點需要注意的是,我們可以使用“fromt”“to”來代表一個動畫是從哪開始,到哪結(jié)束,也就是說這個 "from"就相當(dāng)于"0%"而"to"相當(dāng)于"100%",值得一說的是,其中"0%"不能像別的屬性取值一樣把百分比符號省略,我們在這里必須加上百分符號(“%”)如果沒有加上的話,我們這個keyframes是無效的,不起任何作用。因為keyframes的單位只接受百分比值??匆幌戮唧w的代碼:

@keyframes IDENT {
    from {
        Properties:Properties value;
    }
    Percentage {
        Properties:Properties value;
    }
    to {
        Properties:Properties value;
    }
}
/*或者全部寫成百分比的形式:*/
@keyframes IDENT {
    0% {
        Properties:Properties value;
    }
    Percentage {
        Properties:Properties value;
    }
    100% {
        Properties:Properties value;
    }
}

其中IDENT是一個動畫名稱,你可以隨便取,當(dāng)然語義化一點更好,Percentage是百分比值,我們可以添加許多個這樣的百分比,Properties為CSS的屬性名,比如說left,background等,value就是相對應(yīng)的屬性的屬性值。

2.3.2.2 JS實現(xiàn)

利用JavaScript實現(xiàn)動畫,可以采用開源的JavaScript動畫庫或框架進(jìn)行實現(xiàn),例如:Anime.js或者TweenJS 下面我們以Anime.js為例進(jìn)行演示如何實現(xiàn)一個補(bǔ)間動畫。

一定程度上,anime.js也是一個CSS3動畫庫,適用所有的CSS屬性,并且實現(xiàn)的@keyframes 能更方便的實現(xiàn)幀動畫,替代CSS3復(fù)雜的定義方式。使用對象數(shù)組的形式定義每一幀。

戳我:keyframes實例

anime({ 
    targets: 'div', 
    translateX: [ 
        { value: 250, duration: 1000, delay: 500, elasticity: 0 }, //第一幀 
        { value: 0, duration: 1000, delay: 500, elasticity: 0 } //第二幀 
    ] 
}) //這個例子實現(xiàn)了目標(biāo)元素在兩幀中實現(xiàn)水平位移

提供的Timeline能實現(xiàn)更為復(fù)雜的動畫效果,通過這個Timeline,我們可以維護(hù)不同的動畫之間的關(guān)系,進(jìn)而通過多個不同的動畫組成一個更為復(fù)雜的動畫。

戳我:Timeline實例

var myTimeline = anime.timeline(); 
//通過.add()方法添加動畫 
myTimeline 
.add({ 
    targets: '.square', 
    translateX: 250 
}) 
.add({ 
    targets: '.circle', 
    translateX: 250 
}) 
.add({ 
    targets: '.triangle', 
    translateX: 250 
});

2.3.3 SVG動畫

當(dāng)我們在實現(xiàn)動畫的時候,慢慢會發(fā)現(xiàn),大部分的元素都是圖片,而且圖片是提前預(yù)設(shè)好的,不能更改,只能用新的圖片替換,例如當(dāng)我們要實現(xiàn)微笑動畫的時候,需要畫兩張圖,一幅是閉著嘴的,一幅是張嘴笑的,然后逐幀播放。這樣的畫面當(dāng)你有足夠多幀圖片的時候,并不會看出生硬,一旦低于 24 幀就是變得不自然了,那怎么在不增加工作量的前提下,實現(xiàn)流暢的變化呢?我們將關(guān)鍵幀動畫的思維嫁接到元素自身扭曲變化上,就催生出了「柔性動畫」的概念。

2.3.3.1 SVG動畫講解

(圖片來源于:GSAP官網(wǎng))

從上圖可以看出,元素之間是可以相互變化的,而且非常的流暢,這樣的動畫并不需要 canvas 這種重武器,簡單的 DOM 就可以實現(xiàn),SVG 真的是一個神器,不僅在實現(xiàn)圖標(biāo),字體上特點鮮明,在實現(xiàn)柔性動畫方面也獨樹一幟。

SVG 依然是 DOM ,他有自己獨有的 Animation 標(biāo)簽,但也支持 CSS 的屬性,其實現(xiàn)動畫的本質(zhì)是依賴于線條和填充,線條的變化,導(dǎo)致填充區(qū)域的改變,從而引起形狀的變化。而線條則依賴于路徑和錨點,路徑和錨點的改變,直接影響了線條的變化。

可以用AI等SVG編輯工具生成SVG圖片后,配合anime.js、GSAP等現(xiàn)有庫進(jìn)行動畫制作。

下面我們通過anime.js來實現(xiàn)一個SVG路徑動畫.

SVG 繪制路徑

戳我:SVG實例

var path = anime.path('.motion-path-demo path');


anime({
  targets: '.motion-path-demo .el',
  translateX: path('x'),
  translateY: path('y'),
  rotate: path('angle'),
  easing: 'linear',
  duration: 2000,
  loop: true
});

(圖片來源于:animejs官網(wǎng))

2.3.4 骨骼動畫

SVG 實現(xiàn)的動畫比較局部和小巧,使用范圍也比較狹窄,但是當(dāng)我們實現(xiàn)復(fù)雜的柔性動畫,甚至游戲的時候,就還是需要用骨骼動畫來實現(xiàn)。

(圖片來源于:DragonBones官網(wǎng))

從上圖我們可以看到龍的翅膀是一張圖片,但是可以通過圖片的局部的扭曲和變形,來實現(xiàn)煽動翅膀時帶來的肌肉收縮和舒張。這樣的動畫是怎么實現(xiàn)的呢?這就要引出骨骼動畫中,一個非常重要的概念:網(wǎng)格。

這里我們比較淺顯的討論下這個概念,要實現(xiàn)圖片的局部變化,我們就要把圖片分塊,分的每一塊就稱為網(wǎng)格,每個網(wǎng)格都有自己的頂點和邊,頂點的位移會引起網(wǎng)格形狀的變化,形狀的變化就會帶來所附屬的圖片的變化。網(wǎng)格的概念是不是很像路徑和錨點,不論怎樣的技術(shù),在實現(xiàn)邏輯上都大同小異,重要的不是一直盯著不同和變化的部分,而是發(fā)現(xiàn)那些不變的地方,才能達(dá)到觸類旁通的效果。

制作這樣的動畫并不復(fù)雜,你可以使用類似 Spine 和 DragonBones 這樣的工具,但是做動畫真的是一個體力活,你需要不斷的調(diào)試,以求達(dá)到一種讓人看起來舒服的狀態(tài)。

2.3.4.1 骨骼動畫講解

骨骼動畫就是把角色的各部分身體部件圖片綁定到一根根互相作用連接的“骨頭”上,通過控制這些骨骼的位置、旋轉(zhuǎn)方向和放大縮小而生成的動畫。

我們常說的骨骼動畫一般分為兩個部分:

  1. 骨架(Skeleton)

  2. 蒙皮(Skin)

骨架涉及的數(shù)據(jù)包括兩個:

  • 一是骨架的拓?fù)浣Y(jié)構(gòu)(連接、父子關(guān)系)。

  • 二是骨架的各種pose,也就是每個動作對應(yīng)的整個骨架的位置信息。

蒙皮則表達(dá)的是依附在骨骼上的頂點的信息。

骨骼綁定的過程就是確定每個頂點受哪幾根骨骼的影響,每根骨骼影響的權(quán)重有多大,譬如肘部的皮膚可能同時受大臂和小臂兩根骨頭的影響,而遠(yuǎn)離手肘的部分可能就只受小臂骨頭影響。一般在3D骨骼動畫里,每個頂點最多支持4-8根骨骼同時影響它就已經(jīng)可以很精確地表達(dá)整個蒙皮的效果了。

  • 骨骼動畫的優(yōu)勢:

骨骼動畫比傳統(tǒng)的逐幀動畫要求更高的處理器性能,但同時它也具有更多的優(yōu)勢:

  1. 動畫更加生動逼真。

  2. 圖片資源占最小的存儲空曠:骨骼動畫的圖片容量可以減少90%(配置文件H5的壓縮方案后面詳解)。

  3. 動畫切換自動補(bǔ)間:過渡動畫自動生成,讓動作更加靈動。

  4. 骨骼可控 :可以通過代碼控制骨骼,輕松實現(xiàn)角色裝備更換,甚至可對某骨骼做特殊控制或事件監(jiān)聽。

  5. 骨骼事件幀:動畫執(zhí)行到某個動作或某個幀,觸發(fā)自定義事件行為。

  6. 動作數(shù)據(jù)繼承:多角色可共用一套動畫數(shù)據(jù)。

  7. 可結(jié)合物理引擎和碰撞檢測。

2.3.4.2 骨骼動畫制作

首先我們來了解一下,骨骼動畫是如何進(jìn)行制作的:

制作骨骼動畫主要是使用 Spine 和 DragonBones 這樣的工具進(jìn)行制作。

  • DragonBones

(圖片來源于:DragonBones官網(wǎng))

DragonBones是從Flash動畫開始創(chuàng)作的,初衷是減小資源量,同時實現(xiàn)更為細(xì)粒度的動作(比如交互式的),讓美術(shù)從繁瑣的逐幀繪制Sprie Sheet的工作中解放出來,所以它把一個角色每一幀的sprite sheet拆分成一個個更小的基本圖塊,譬如胳膊,腿,軀干等等,而每個基本圖塊仍然是最小的可控制單位。

以下游戲&渲染引擎都支持渲染DragonBones導(dǎo)出的文件:

(圖片來源于:DragonBones官網(wǎng))

  • Spine

(圖片來源于:Spine官網(wǎng))

Spine 是一款針對游戲開發(fā)的 2D 骨骼動畫編輯工具。Spine 旨在提供更高效和簡潔 的工作流程,以創(chuàng)建游戲所需的動畫。

業(yè)界收費專業(yè)2D骨骼動畫編輯工具,動畫設(shè)計師推薦易用穩(wěn)定,以下游戲&渲染引擎都支持渲染Spine導(dǎo)出的文件:

(圖片來源于:Spine官網(wǎng))

下面我們來制作一個骨骼動畫小案例

  • 創(chuàng)建骨骼

首先我們需要創(chuàng)建手部的骨骼,如下圖所示:

  1. 1確保左上角為SETUP模式

  2. 確保選中右邊視圖中的根骨骼,創(chuàng)建骨骼時必須要選中父骨骼

  3. 單擊左下角的Create按鈕

  4. 開始依次創(chuàng)建出5根骨骼

  • 創(chuàng)建蒙皮網(wǎng)格

然后我們需要給手部創(chuàng)建蒙皮網(wǎng)格(MESH),如下圖所示:

首先,單擊創(chuàng)建骨骼的Create按鈕,退出骨骼創(chuàng)建模式

  1. 選中手部貼圖(Attachment)

  2. 勾選其底部的Mesh選項

  3. 單擊右下角的Edit按鈕

  4. 呼出了Edit Mesh菜單

  5. 勾選Edit Mesh菜單中的Deformed選項

  6. 單擊Edit Mesh菜單中的Create按鈕

  7. 開始在手部創(chuàng)建網(wǎng)格頂點

  8. 可以單擊Edit Mesh菜單中的Modify按鈕對頂點進(jìn)行位移

  • 設(shè)置網(wǎng)格點權(quán)重

我們需要給網(wǎng)格頂點設(shè)置各個骨骼的權(quán)重,整個過程如下圖所示:


首先,關(guān)閉Edit Mesh菜單

  1. 確認(rèn)勾選的還是手部的貼圖

  2. 單擊左下角的Weights按鈕,呼出Weights菜單

  3. 單擊Weights菜單底部的Bind按鈕,來綁定骨骼

  4. 選擇手部的五根骨骼,直到它們都出現(xiàn)Weights菜單里,注意不同的骨骼顏色是不一樣的

  5. 單擊Weights菜單的Auto按鈕或者按`esc`鍵,來觸發(fā)Spine的自動權(quán)重計算

  6. 勾選Weights菜單的Overlay,我們可以看到綁定后的權(quán)重?zé)崃D

  • 動起來!

現(xiàn)在我們要讓手動起來了,我們只展示一個彎曲手臂的動畫即可。

首先,我們需要設(shè)置關(guān)鍵幀,讓我們在第1幀和第30幀設(shè)置好關(guān)鍵幀,這兩個關(guān)鍵幀對應(yīng)的手臂位置是完全一樣的,因為我們需要循環(huán)播放動畫。

具體步驟如下圖:

  1. 確保左上角的模式處于ANIMATE模式

  2. 選中手部的五根骨骼(按住`cmd`鍵或`control`鍵依次點選)

  3. 選中第0幀

  4. 單擊Rotate下的鑰匙按鈕,我們對手臂的旋轉(zhuǎn)屬性設(shè)置關(guān)鍵幀

  5. 選擇第30幀

  6. 重復(fù)第4步的操作,使第30幀的關(guān)鍵幀與第0幀完全相同

接下來我們只需輕輕旋轉(zhuǎn)手臂,并在0-30幀中間找一個幀當(dāng)做關(guān)鍵幀即可:我們選擇第15幀作為中間的關(guān)鍵幀。

  1. 選擇第15幀

  2. 確保Rotate按鈕被選中

  3. 向上旋轉(zhuǎn)5根骨骼到一個角度

  4. 按下K幀按鈕進(jìn)行關(guān)鍵幀設(shè)置

  5. 按下播放按鈕來預(yù)覽動畫

額外的,我給另一只手、嘴巴、臉部和頭發(fā)都做了MESH,以下是動畫的效果圖:

2.3.4.3 前端展示骨骼動畫

用Spine將制作好的骨骼動畫進(jìn)行導(dǎo)出輸出資源(合圖信息文件:atlas;動畫信息文件:json,圖片合圖:png),將這些資源交由前端進(jìn)行展示。

前端開發(fā)根據(jù)Spine或者DragonBones能夠支持的渲染引擎,在項目中導(dǎo)入渲染引擎進(jìn)行展示骨骼動畫。

2.3.5 3D動畫

前端3D動畫實現(xiàn)可以通過perspective屬性操作用CSS 3D來實現(xiàn),或者直接借助開源的Three.js開源庫進(jìn)行實現(xiàn)。

由于3D動畫涉及的內(nèi)容較多,篇幅有限,后面我們將專門開一章來講解前端3D動畫。

三、現(xiàn)有方案總結(jié)

3.1 純CSS實現(xiàn)

適合場景: 簡單的展示型動畫

使用transition\animation屬性,設(shè)置相應(yīng)的關(guān)鍵幀狀態(tài),并且借助一些緩動函數(shù)來進(jìn)行實現(xiàn)一些簡單化的動畫。

優(yōu)點:開發(fā)成本低,不需要導(dǎo)入任何額外的依賴包

缺點與不足:只能夠勝任做一些比較簡單化的動畫,無法實現(xiàn)一些過于負(fù)責(zé)的動畫。

3.2 Anime.js

適用場景: 簡單的展示型動畫+弱交互型動畫

Anime.js是一個輕量級的js驅(qū)動的動畫庫,主要的功能有:

  1. 支持keyframes,連接多個動畫

  2. 支持Timeline,為實現(xiàn)更為復(fù)雜的動畫提供了可能

  3. 支持動畫狀態(tài)的控制playback control,播放,暫停,重新啟動,搜索動畫或時間線。

  4. 支持動畫狀態(tài)的callback,在動畫開始,執(zhí)行中,結(jié)束時提供回調(diào)函數(shù)

  5. 支持SVG動畫

  6. 可以自定義貝塞爾曲線

  7. 任何包含數(shù)值的DOM屬性都可以設(shè)置動畫

功能介紹:

一定程度上,anime.js也是一個CSS3動畫庫,適用所有的CSS屬性,并且實現(xiàn)的@keyframes能更方便的實現(xiàn)幀動畫,替代CSS3復(fù)雜的定義方式。使用對象數(shù)組的形式定義每一幀。

戳我:keyframes實例

anime({ 
    targets: 'div', 
    translateX: [ 
        { value: 250, duration: 1000, delay: 500, elasticity: 0 }, //第一幀 
        { value: 0, duration: 1000, delay: 500, elasticity: 0 } //第二幀 
    ] 
}) //這個例子實現(xiàn)了目標(biāo)元素在兩幀中實現(xiàn)水平位移

提供的Timeline能實現(xiàn)更為復(fù)雜的動畫效果,通過這個Timeline,我們可以維護(hù)不同的動畫之間的關(guān)系,進(jìn)而通過多個不同的動畫組成一個更為復(fù)雜的動畫。

戳我:Timeline實例

var myTimeline = anime.timeline(); 
//通過.add()方法添加動畫 
myTimeline 
.add({ 
    targets: '.square', 
    translateX: 250 
}) 
.add({ 
    targets: '.circle', 
    translateX: 250 
}) 
.add({ 
    targets: '.triangle', 
    translateX: 250 
});

動畫播放的控制,常見的有暫停,重播,繼續(xù),動畫狀態(tài)的跟蹤,自動播放,循環(huán)次數(shù),抖動效果

戳我:playback controls實例

為動畫提供了回調(diào)函數(shù),在動畫或時間線完成的開始,期間或之時執(zhí)行回調(diào)函數(shù)。

戳我:callback實例

var myAnimation = anime({ 
    targets: '#begin .el', 
    translateX: 250, 
    delay: 1000, 
    begin: function(anim) { // callback 
        console.log(anim.began); // true after 1000ms 
    } 
});

支持promise,動畫結(jié)束后,調(diào)用anime.finished會返回一個promise對象。

戳我:promise實例

支持svg繪制路徑,目前不支持canvas繪制。

戳我:SVG實例

對于input這樣帶有數(shù)值的元素標(biāo)簽,也可以通過anime實例來設(shè)置動畫。

戳我:DOM ATTRIBUTES實例

anime({ 
    targets: input, 
    value: 1000, // Animate the input value to 1000 
    round: 1 // Remove decimals by rounding the value 
});

優(yōu)點:

  • 顯而易見,anime.js不僅實現(xiàn)了CSS3動畫的深度封裝,更多的是通過js驅(qū)動來實現(xiàn)操作動畫的狀態(tài),timeline實現(xiàn)了對于多個分支動畫的管理,對于實現(xiàn)更為復(fù)雜的動畫提供了可能。

  • 通過anime.js提供的playback controls和callback,同時對于promise的支持,讓我們對于動畫的簡單交互有了操作的空間。

  • 雖然不支持canvas,但是支持svg繪制路徑。

  • 瀏覽器兼容性比較好,Android 4以上全部支持。

缺點:

Anime.js做展示型動畫是可以勝任的,但是對于特別復(fù)雜的動畫也是不太能夠?qū)崿F(xiàn),在做交互性動畫方面還是需要看場景,它更多適合做一些小型的交互動畫,類似于通過觸摸屏幕踢足球這種強(qiáng)交互的,anime.js就不是很有優(yōu)勢了。

3.3 Lottie

適用場景: 復(fù)雜的展示型動畫

通過 AE 上的 Bodymovin 插件將 AE 中制作好的動畫導(dǎo)出成一個 json 文件,通過Lottie對JSON進(jìn)行解析,最后以SVG/canvas/html的方式渲染動畫。

能夠完好的展示設(shè)計師設(shè)計的各種各樣復(fù)雜的動畫。

優(yōu)點:

  • 跨平臺,一次繪制、一次轉(zhuǎn)換、隨處可用。

  • 文件更小,獲取AE導(dǎo)出的JSON,最后通過lottie渲染為canvas/svg/html格式。

  • 可以通過api操縱動畫的一些屬性,比如動畫速度;添加動畫各個狀態(tài)的回調(diào)函數(shù)。

  • 動畫都是在After Effects中創(chuàng)建的,使用Bodymovin導(dǎo)出,并且本機(jī)渲染無需額外的工程工作。

  • 解放前端工程師的生產(chǎn)力,提高設(shè)計師做動效的自由度。

缺點:

  • Bodymovin 插件待完善,仍然有部分 AE 效果無法成功導(dǎo)出。

  • 對于交互方面支持的還不是很好,更多的是用來展示動畫。

  • Lottie 對 json 文件的支持待完善,目前有部分能成功導(dǎo)出成 json 文件的效果在移動端上無法很好的展現(xiàn)。

  • 很多AE的效果是不支持的 查看支持的特性:Supported Features

3.4 PixiJs

**適用場景: **交互型動畫,動畫小游戲

PixiJS是一個2D 渲染引擎, Pixi 主要負(fù)責(zé)渲染畫面。可以創(chuàng)建豐富的交互式圖形,動畫和游戲,而無需深入了解WebGL API或處理瀏覽器和設(shè)備兼容性的問題。與此同時,PixiJS具有完整的WebGL支持,如果需要,可以無縫地回退到HTML5的canvas。PixiJs默認(rèn)使用WebGL渲染,也可以通過聲明指定canvas渲染,WebGL在移動端Android 4.4 browser并不支持,不過可以使用canvas優(yōu)雅降級。

特性(摘自官方DOCS):

  • 支持WebGL渲染

  • 支持canvas 渲染(官方稱PixiJS在canvas渲染方面現(xiàn)在是最快的)

  • 非常簡單易用的API

  • 豐富的交互事件,比如完整的鼠標(biāo)和移動端的觸控事件

  • Pixi使用和 canvas Drawing幾乎一致的 api,但不同于 canvas 的繪畫 api,使用 Pixi 繪制的圖形是通過 WebGL 在 GPU 上渲染

  • 還有一系列特性需要在學(xué)習(xí)PixiJs之后了解

優(yōu)點:

  • 最大優(yōu)勢莫過于通過WebGL來調(diào)用GPU渲染動畫,這樣極大的提升了性能。

  • 無需深入了解WebGL API或者是瀏覽器兼容性(因為下面這條原因)。

  • 支持canvas回退,當(dāng)前設(shè)備不支持WebGL時,PixiJs會使用canvas渲染動畫。

  • 完整的DOCS,比較活躍的社區(qū),有利于深入的學(xué)習(xí)。不過我感覺PixiJs學(xué)習(xí)成本相對來說還是很高的。

缺點:

  • 首先是兼容的問題,WebGL在Android 4.4 是不支持的,只能使用canvas進(jìn)行降級。

  • Pixi 主要負(fù)責(zé)渲染畫面,很多其它功能開發(fā)者得自己寫或搭配其它庫來使用,不過按照目前來看,是滿足我們的需求的。

性能:

對于手機(jī)版本Android4.4 以上的手機(jī),除了代碼層面造成的性能不足,通過WebGL調(diào)用GPU渲染,性能還是有保障的。然而對于Android4.4只能使用canvas渲染,性能還是要看動畫的復(fù)雜度,以及代碼的優(yōu)化

3.5 總結(jié)

簡單的展示型動畫:

對于比較簡單的動畫,我們可以先嘗試使用原生CSS的transition\animation屬性來進(jìn)行實現(xiàn)。

簡單的展示型動畫+弱交互:

對于簡單的動畫展示并且需要有簡單的交互行為,比如用戶點擊一下暫停執(zhí)行相應(yīng)操作,待操作完成繼續(xù)播放動畫,交互方面比較偏弱,可以采用Anime.js的方案。

Anime.js不僅僅支持所有的CSS屬性,而且可以通過Timeline,callback, playback controls來控制動畫執(zhí)行的各個狀態(tài),并且Anime.js可以配合實現(xiàn)SVG動畫。

復(fù)雜的展示型動畫:

  1. 如果所需的資源很小,可以先考慮使用GIF動圖或者逐幀動畫CSS實現(xiàn);

  2. 如果所需的資源較大,可以使用Lottie方案,然后設(shè)計同學(xué)用AE到處動畫json,將動畫還原為svg/canvas/html。

強(qiáng)交互&互動小游戲&骨骼動畫:

  1. 對于交互場景比較負(fù)責(zé)或者需要做一個小游戲,可以采用PixiJs,通過WebGL來渲染,利用硬件資源,極大的提升性能,在兼容性方面,對于不支持WebGL的瀏覽器,可以使用canvas渲染來平穩(wěn)回退;

  2. 如果是需要展示骨骼動畫,可以通過PixiJs方案進(jìn)行渲染由Spine或DragonBones輸出的文件。
    01.gif
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容