實作: javascript
最近業務需要,做了好多互動動畫和過渡動畫。有canvas的,有dom的,也有css的,封裝的起點都不一樣,五花八門。
而靜下來仔細想想,其實不管怎麼實作,本質都是一樣。可以抽象一下。
<code>view = f(s)</code>
其中<code>s</code>指某些狀态,大多數情況下都是時間。
大家來跟我一起念 : 動 ~ 畫 ~
對對對,就是動起來的畫面。
不知道大家小時候玩過下面這個沒有...
小本本一翻起來,畫面快速的變化,看起來就像在動一樣,當時感覺超級神奇。
當然現在大家都明白了這是視覺暫留,先驅依據這個造出了顯示器,也造就了我們現在的動畫模式。
是以,動畫就是一組不連續的畫面快速播放,利用腦補形成的動起來的錯覺。
現在大家腦補一個 真空中勻速直線運動的 小球
然後掏出一個相機,對它一頓瘋狂拍攝。在下手手法不佳,拍的一點也不均勻。
我把每一次拍照的行為稱為一次 觀測
例子裡的小球的運動隻受到時間的影響
不論觀測的次數有多少,都不會影響小球的運動過程
每次的觀測都會産生一個畫面(<code>view</code>)
把每次觀測的時間<code>t</code>和小球的位置<code>x</code>記錄下來。
就可以得出
<code>(x - xstart) = v * (t - tstart)</code>
=> <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 -> 1,1像素的動畫沒法看,我就不擱demo了,徐徐圖之。
上文實作了0到1的動畫,現在我們來實作一個數字從10變成99的dom動畫。
為了便于抽象,我們把 [ xstart , xend ] 映射到 [ 0 , 1 ] ,這一過程被稱為歸一化
我把其中的<code>p</code>稱為 進度
現在需要提供 [ 0 , 1 ] -> [ xstart , xend ] 的映射,我叫它複原過程
我們用 <code>x = fu(p)</code> 來表示這一過程。
什麼?單詞複原不是fu開頭?沒學過拼音嗎?
比如這裡的 <code>[ 0 , 1 ] -> [ 10 , 99 ]</code> 就是 <code>x = fu(p) = 10 + p * ( 99 - 10 )</code>
舉例來說,一個位移動畫,物件的軌迹可以形成一條位移曲線。而時間曲線就抽象了很多。
從前文的例子中,我們的動畫叫做線性動畫,就像是“勻速直線運動”的小球一樣,運動的程序始終如一。
想象我們在每一幀渲染的時候,都對<code>p</code>進行一定的處理 <code>q = easing(p)</code>,那線性動畫就是 <code>easing(p) = p</code>
如果要用例子來描述的話,大概就是這樣。
現在我們要模拟開始逐漸加速的場景,差不多就是下圖的樣子
<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
好的,上面都是玩具,接下來讓我們來做一點 大人的事情吧
正好,我手上有個大餅。
ued表示:你不能直接把這個餅放到頁面上。
要!加!特!技!
吓得我趕緊new了一個image
準備一個canvas,洗淨,晾幹,備用。
根據我多年的經驗,要在整個canvas上搞事,一般會拿一個離屏canvas來提供一些内容。然後直接把離屏canvas draw在可視canvas上。
這一步我們封在 animation 類上
這樣做的話,我們就可以在此基礎上封裝各種需要,像什麼百葉窗動畫,扇形動畫,中心放射動畫之類的,隻需要提供一個帶繪制函數的柯裡化即可。
正如上面所說,我們在此基礎上封裝一個 wavec 方法。
實作方法
在可視canvas上計算出一個扇形區域并裁切畫布
把暫存在離屏canvas的内容轉印到可視canvas上
這一步提供了一個預設的 easing = p=>p ,即線性動畫作為預設值。
這樣我們就設計了一個api <code>animation.wavec = function( canvas , duration , easing )</code> 隻要簡單的提供 canvas , 持續時長 ,就可以完成一個扇形動畫了。
把剛才洗淨的 canvas 和 img 重新撿回來。
時間動畫總是能抽象為 view = f( easing(t) ) 的形式
通過在animation上提供不同粒度的封裝,可以滿足不同層次的定制需求
本文隻講述了時間動畫的一種抽象,但業務千千萬萬,還不夠。
比如有些業務會需要在動畫的過程中終止
有時終止後還會需要原路後退 (反向播放動畫)
動畫總是異步的,為了更好的開發體驗,最好是可以封一套和promise相關的api,便于提升開發體驗,異步管理,以及其他體系融合。
今天就到這裡了,客官,下次再來喲 ~~