天天看點

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

實作: javascript

最近業務需要,做了好多互動動畫和過渡動畫。有canvas的,有dom的,也有css的,封裝的起點都不一樣,五花八門。

而靜下來仔細想想,其實不管怎麼實作,本質都是一樣。可以抽象一下。

<code>view = f(s)</code>


其中<code>s</code>指某些狀态,大多數情況下都是時間。

大家來跟我一起念 : 動 ~ 畫 ~

對對對,就是動起來的畫面。

不知道大家小時候玩過下面這個沒有...

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

小本本一翻起來,畫面快速的變化,看起來就像在動一樣,當時感覺超級神奇。

當然現在大家都明白了這是視覺暫留,先驅依據這個造出了顯示器,也造就了我們現在的動畫模式。

是以,動畫就是一組不連續的畫面快速播放,利用腦補形成的動起來的錯覺。

現在大家腦補一個 真空中勻速直線運動的 小球

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

然後掏出一個相機,對它一頓瘋狂拍攝。在下手手法不佳,拍的一點也不均勻。

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

我把每一次拍照的行為稱為一次 觀測

例子裡的小球的運動隻受到時間的影響

不論觀測的次數有多少,都不會影響小球的運動過程

每次的觀測都會産生一個畫面(<code>view</code>)

把每次觀測的時間<code>t</code>和小球的位置<code>x</code>記錄下來。

就可以得出

<code>(x - xstart) = v * (t - tstart)</code>

=&gt; <code>x = v * (t - tstart) + xstart</code>

這樣就得到了一個 <code>view = f(t)</code> 的具體表現

我把 <code>f(t)</code> 稱為對動畫的 描述,它建立起了視圖和時間的關聯

我們已經有了足夠的概念,在業務中,我們實作一個動畫:

抽象出一個動畫描述

設定一個開始時間

不斷進行觀測

把觀測結果寫入視圖

因為螢幕的重新整理總是有一個頻率,就好像是螢幕對視圖的觀測一樣,過多的觀測其實沒有太大意義,最好,能和螢幕的重新整理率一緻(<code>requestanimationframe</code>)。

talk is cheap

為了貼合浏覽器的重新整理頻率,我們使用 requestanimationframe 方法。

這個方法可以在下一次螢幕重新整理前注冊一個回調。

接下來我們先定一個小目标,實作一個從小球從0移動到1的動畫 (歸一化)

持續時間為 <code>duration</code>

顯然 <code>f(t) = (t - tstart) / duration</code>;

來定義一下行為

好,一個基本的 animation 類就完成了,我們來使用一下。

0 -&gt; 1,1像素的動畫沒法看,我就不擱demo了,徐徐圖之。

上文實作了0到1的動畫,現在我們來實作一個數字從10變成99的dom動畫。

為了便于抽象,我們把 [ xstart , xend ] 映射到 [ 0 , 1 ] ,這一過程被稱為歸一化

我把其中的<code>p</code>稱為 進度

現在需要提供 [ 0 , 1 ] -&gt; [ xstart , xend ] 的映射,我叫它複原過程

我們用 <code>x = fu(p)</code> 來表示這一過程。

什麼?單詞複原不是fu開頭?沒學過拼音嗎?

比如這裡的 <code>[ 0 , 1 ] -&gt; [ 10 , 99 ]</code> 就是 <code>x = fu(p) = 10 + p * ( 99 - 10 )</code>

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

舉例來說,一個位移動畫,物件的軌迹可以形成一條位移曲線。而時間曲線就抽象了很多。

從前文的例子中,我們的動畫叫做線性動畫,就像是“勻速直線運動”的小球一樣,運動的程序始終如一。

想象我們在每一幀渲染的時候,都對<code>p</code>進行一定的處理 <code>q = easing(p)</code>,那線性動畫就是 <code>easing(p) = p</code>

如果要用例子來描述的話,大概就是這樣。

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

現在我們要模拟開始逐漸加速的場景,差不多就是下圖的樣子

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

<a href="http://cubic-bezier.com/#1,0,1,1">http://cubic-bezier.com/#1,0,1,1</a>

也就是 <code>easing(p) = p*p</code>;

好,修改一下前面的demo

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

好的,上面都是玩具,接下來讓我們來做一點 大人的事情吧

正好,我手上有個大餅。

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

ued表示:你不能直接把這個餅放到頁面上。

要!加!特!技!

吓得我趕緊new了一個image

準備一個canvas,洗淨,晾幹,備用。

根據我多年的經驗,要在整個canvas上搞事,一般會拿一個離屏canvas來提供一些内容。然後直接把離屏canvas draw在可視canvas上。

這一步我們封在 animation 類上

這樣做的話,我們就可以在此基礎上封裝各種需要,像什麼百葉窗動畫,扇形動畫,中心放射動畫之類的,隻需要提供一個帶繪制函數的柯裡化即可。

正如上面所說,我們在此基礎上封裝一個 wavec 方法。

實作方法

在可視canvas上計算出一個扇形區域并裁切畫布

把暫存在離屏canvas的内容轉印到可視canvas上

這一步提供了一個預設的 easing = p=&gt;p ,即線性動畫作為預設值。

這樣我們就設計了一個api <code>animation.wavec = function( canvas , duration , easing )</code> 隻要簡單的提供 canvas , 持續時長 ,就可以完成一個扇形動畫了。

把剛才洗淨的 canvas 和 img 重新撿回來。

深入淺出JS動畫到底什麼是動畫?純粹的實作 - 一個數字動畫改變時間 - 動畫的時間曲線與緩動效果業務需要的封裝 - 一個扇形動畫作為例子總結與後續

時間動畫總是能抽象為 view = f( easing(t) ) 的形式

通過在animation上提供不同粒度的封裝,可以滿足不同層次的定制需求

本文隻講述了時間動畫的一種抽象,但業務千千萬萬,還不夠。

比如有些業務會需要在動畫的過程中終止

有時終止後還會需要原路後退 (反向播放動畫)

動畫總是異步的,為了更好的開發體驗,最好是可以封一套和promise相關的api,便于提升開發體驗,異步管理,以及其他體系融合。

今天就到這裡了,客官,下次再來喲 ~~

繼續閱讀