哇牛叉學院之超簡緩動動畫小演算法

哇牛叉學院之超簡緩動動畫小演算法

本文作者:張鑫旭

原創聲明:本文為閱文前端團隊 YFE 成員出品,請尊重原創,轉載請聯繫公眾號 ( id: yuewen_YFE ) 獲取授權,並註明作者、出處和鏈接。

本集出場人物:張小師、波波、神官、阿坤、螢火

友情客串:張小凡

特別感謝:康康兔

夜漸漸暗了下來,在例行任務結束後,『張小師』和隊友們並沒有像以往一樣,積極忙忙趕回「哇牛叉學院」,因為,這是過年前的最後一次任務,等回到學院,大家可能就要暫時分別一段時間。這一年的奮戰已經夠辛苦了,所以大家覺得可以一起找個地方放鬆下。「要不我們去「青雲餐館」吃烤兔子?」『波波』提議,「好啊!」眾人一拍即合!

「青雲餐館」坐落於青雲山腳下,互聯 14 年由蕭鼎所建,主廚名叫張小凡,一手烤兔子的絕活遠近聞名,現烤現吃,不過張小凡本人是不出面的,都是學徒們出來現烤,除非是「閱之國」皇親國戚這樣的重要人物或者出重金的商人才有此待遇。

當然,『張小師』一行人前去吃烤兔子就是單純吃烤兔子,並未想過與見到傳說中的主廚張小凡,更沒有奢想張小凡會親自為這麼幾個普通的客人烤兔子。但是,這一天發生的事情完全超出了大家的預料。

『波波』的技能是[人品加護],也就是任務攻略完畢 100% 撿到寶貝。但很多時候,撿到的這些寶貝似乎是假寶貝,比方說,「服用後 100% 腹瀉的精靈石」,「遇水即化的泳衣」以及「先脫髮再生髮秘制生髮靈」等等,並不實用,於是都給賣了。

但是,這一次,[人品加護]似乎迎來了大爆發……

眾人包間就坐,點好了大號烤兔子,邊閑聊邊等待廚師出現。很快,包間的門推開了,一個略顯帥氣的身影出現了,戴著高高的廚師帽,推著串著整隻兔子烤架進來了,一切都很自然一切都很平常。但是,當廚師抬起頭的時候,不知誰突然尖角了一聲,眾人面面相覷,此不知哪裡竄出一個聲音,「你……是不是就是那個傳說中比宋仲基還帥比李易峰還酷廚藝出神入化「青雲餐館」大當家,張小凡?」

廚師微微一笑,默不作聲。「沒錯,一定是你!我『阿坤』的[記憶宮殿]技能之一就是過目不忘。2 年前我們「閱之國」成立時候照片中就有你。」

「喔噢,不會吧……」大家都驚訝地看著彼此。因為現在這種狀況就等同於在薛之謙開的火鍋店吃火鍋但是薛之謙親自為你服務,大家都不敢相信,紛紛開始索要簽名。

「諸位稍安,再耽擱就錯過兔肉最佳燒烤時間了,我們不妨先飽食慾,簽名之事可以等兔肉烤完再說也不遲,不知可否?」

「恩,可以的」,『神官』擺了擺手,讓大家安靜。

然後,廚師開始了嫻熟的烤兔子製作……

此時,『波波』沒忍住,問道:「請問,你現在是在拍真人秀節目嗎?否則您這樣的大人物怎麼會親自給我們烤兔子呢?」

「這個嘛,說來也慚愧」,廚師邊給兔子劃口子邊說道,「本來我有 6 名弟子,現在要過年了,客人也少,就有 2 人我讓他先回老家了,剩下的 3 名弟子,一個便秘很多天於是吃腹瀉精靈石,結果沒控制住量,拉到腰都直不起來;還有名弟子更慘,女朋友穿著的他送的泳衣去泡混合溫泉,結果泳衣化掉了,結果跪了一晚上的燒烤架,腿算是廢了;還有個弟子不知服了什麼葯,掉發太厲害,帽子都兜不住,我們做餐飲的最怕食物有頭髮,所以我只能讓他休息幾天;最後 1 個弟子倒是沒什麼事,但是,今天白天基本上客人都是他一個人應付的,已經累得看不清兔子有幾條腿了。所以,現在只能我自己上了,放心,價格還是普通的價格。」說完又是蜜汁微笑。

「哦~~」『波波』若有所思地應道,好像突然想到了什麼。

劃好口子,只見廚師拇指和食指相交成愛心,形成一個比心手勢,然後輕輕一撮,一股森白火焰從指頭冒出。

此技一出,眾人嘩然,要知道,要想將「泉之力」直接轉成火焰可是非常罕見非常高階的技能,這廚師點火如此輕鬆嫻熟,這背後的實力怕是深不可測。更加確信,此人就是張小凡。

然後,只見手指輕輕一彈,原本指尖的火焰瞬間消失,轉而烤架下方出現了薄薄的一層火焰,眾人正奇怪這麼小的火,看上去溫度也不高,怎麼烤兔子的時候,廚師突然解印,速度之快,普通人看上去就是手掌一翻,能力者可以看出有解印,但不知細節,只有『阿坤』憑藉[記憶宮殿]技能看清了巳-未-申-亥-午-寅的解印順序。然後,一股悠長綿延的「泉之力」向烤架下方的火焰奔去,只見火焰立馬有了靈性,將兔子緩緩圍住,黃白火焰有規律地在兔身周圍順時針緩慢旋轉。

「好厲害!」感知敏銳的『起床氣』不由得發出感嘆,「「泉之力」外放已經很難,平常所見外放都是直接剛烈,沒想到居然可以這般由快變慢,同時聚而不散,這要比『螢火』的[激光外放]要難啊!」

「過獎了!」廚師顯得很輕鬆,接著說道,「其實這「泉之力」外放緩動技能看上去很玄奧,實際上並不難學,而且好處不僅僅是可以很好地控制火候,我知道你們經常要抓小精靈,如果「泉之力」發出過於生硬,過於蠻力,除了浪費自己力氣外,還會驚擾到其他精靈而逃逸消失,所以,對於你們日常工作也是很有幫助的。不知道你們有沒有興趣學習,我可以現場教給你們,報酬嘛……

恩,就在「哇牛叉學院」的故事坊中好好宣傳下我的「青雲餐館」,不知成交否?」

「好!」大家異口同聲答到。

「好,正好兔子烤好還需要點時間,我們可以邊聊變等」,廚師邊悠然說道邊悠哉地釋放綿延但厚重的「泉之力」。

「此技能是我年輕獲得的……」

讓交互蓬蓽生輝的超簡緩動小演算法

原理如下:

假設要從位置 A 變化到位置 B,如果是生硬線性運動,則每次移動距離是一樣;如果是緩動,每次移動距離不一樣。那如何才能不一樣呢?很簡單,按比例移動就可以。

例如:每次移動剩餘距離的一半。

對吧,超容易理解的

比方說:你和燒烤架之間距離是 64,每秒移動一半,則,你們之間的距離下一秒就是 32,再下一秒就是 16,然後 8,然後 4,然後 2,然後 1,然後……你們就在一起了。你們在一起的這個過程就是一個典型的先快後慢的緩動運動過程,示意如圖1:

用一個簡單的公式表示就是:

A = A + ( B - A ) / 2

翻譯一下就是:

我下一秒的位置 = 現在位置 + 現在和燒烤架之間距離的一半

是不是很好理解。

而上面的 A=A+(B-A)/2 就是我要分享給大家的即插即用的緩動小演算法。

當然要實際使用還是需要做一點點的處理的,首先,要讓運動連綿不絕,那就離不開定時器,我們可以使用 requestAnimationFrame,對於不支持的瀏覽器,可以使用下面的兼容代碼:

//requestAnimationFrame的兼容處理if(window.requestAnimationFrame{ requestAnimationFrame=function(fn){ setTimeout(fn,17); }; }

然後,實際緩動的比例不會是 2,有可能是 3 或者 4 之類,也是個變數,因此,完整的核心演算法公式是:

A = A + ( B - A ) / rate

於是,我們火烤兔子的緩動釋放效果就可以使用下面的代碼表示:

//泉之力的位置var fireToRabbit = function() { var A = 64, B = 0, rate = 2; var top = function() { A = A + ( B - A ) / rate; //臨界判斷,終止運動 if( A < 1 ) { distance = B; return; } distance = A; //運動gogogo! requestAnimationFrame(top); }; top();};

等比例靠近理論上最終只會無窮靠近,並不會真正的相等,也就是運動永遠沒有結束的時候,所以說需要做一個臨界判斷,也就是距離小到一定數目的時候,直接等於目標值,並終止動畫。例如,上面就是當距離燒烤架的距離小於 1 的時候,直接終止運動,釋放「泉之力」,生成火焰。

if( A < 1 ) { distance = B; return;}

我們最近剛上線不久的新版的起點移動站的返回頂部效果(見圖2)就是使用的這個緩動小演算法。

當然,M 站使用這個動畫小演算法的地方不止這一個,比方說首頁最近閱讀的小球是可以移動的,慣性運動動畫效果就是用的該演算法,還是書籍閱讀頁面水平看書模式時候移來移去的翻頁效果(見圖3),也是此演算法;垂直模式時候點擊翻屏也是該演算法等等,用得非常多。

這種演算法非常簡單實用,項目中經常會不止一個地方使用,於是就出現這樣的問題,如果每次緩動都寫一遍 requestAnimationFrame 和邊界判斷就會很啰嗦的,於是,我們可以把演算法變個身,例如下面這樣:

Math.easeout=function(A,B,rate,callback){ if( A == B || typeof A != number) { return; } B = B || 0; rate = rate || 2; var step = function() { A=A+(B-A)/rate; if( A < 1 ) { callback(B,true); return; } callback(A,false); requestAnimationFrame(step); }; step();};

其中:

  • A 是起始位置;
  • B 是目標位置;
  • rate 是緩動速率;
  • callback 是變化的位置回調,支持兩個參數,valueisEnding,表示當前的位置值(數值)以及是否動畫結束了(布爾值);

於是,我們的返回頂部效果可以這麼使用:

var doc = document.body.scrollTop ? document.body : document.documentElement;Math.easeout(doc.scrollTop,0,4,function(value){ doc.scrollTop = value;});

可以看到效果是一樣的棒棒噠。更關鍵點是我們的演算法可以更簡單地重複利用了,也就是無論是抓小精靈還是燒烤,只能掌握此技能,都能非常方便使用,即插即用。

實際上,業界還有更全面的 Tween.js 標準的緩動演算法,但是,這個演算法是是基於時間來做演算法的,而我剛剛展示的 Math.easeout 演算法是基於緩動速率的,最終的時間是不固定的,受起始和終止位置的距離的影響,也正是這個原因,本文的緩動動畫演算法更好理解更容易記憶,因為現實世界距離是真實的容易感知的,時間是虛幻的無法捕捉的,正好和本文的演算法距離驅動而非時間驅動相映射,容易形成共識。

「恩,就這些,不知大家聽完之後有什麼感想?」

「太厲害了!」『張小師』聽完不禁豎起了大拇指,「真可謂,聽君一席話勝讀十年書!沒想到閣下不僅廚藝了得,這「泉之力」掌控之技也如此出神入化,再下佩服地五體投地,自愧不如!」

「哈哈,那就好,答應我的事情可別忘記哦」,說完,廚師右手手腕一翻,一個罐狀物出現在手中,左手一收,頓時兔身周圍如星雲繞日般的火焰頓時猶如失去了呼吸,瞬間只剩下底部的一層薄薄略帶裊裊的森白之火,然後,右手猛地一震,罐中粉末狀物體悉數迸出,手掌朝下,再迅速翻過來,手上已經空空如也,只有剛剛迸出的調味料猶如時間停止般禁錮懸空在手掌上方,實際上,調味料是被手中散發的「泉之力」包裹起來了,這感知力和操控技術,可以和學院頂級的「品器測量師」相媲美了。然後,只見五指如划水般輕輕撥弄,調味粉末慢慢散開,隱隱中似乎被五道線路引導,均勻地環繞並依次吸附在兔身周圍,並發出輕輕的滋滋聲,很快調味粉末一點不差全部均勻撒完了。

大家正以為已經結束可以開吃的時候,突然,廚師左手用力一捏,再猛然張開,往前一推,底下原本那一層薄薄的懶散的火焰,突然如火龍一樣,隨著「轟」地一聲,迅速爆燃開來,準確講應該是火蛇,周身全部都是熊熊的紅色火焰的火蛇,將兔子緊緊纏繞起來。

這來勢之突然,景色之壯觀,在座不少人都驚呼出聲,正當準備回味的時候,突然廚師左手一收,火蛇頓時散去的無影無蹤,同時,兔身周圍的那些佐料粉末也消失得無影無蹤,整個房間只剩下油火冷卻的滋滋聲以及按耐不住四處散逸的兔肉的香味。

「好了,可以吃了!」

大家這才晃過神來,開始立馬紛紛拿起手機拍照留戀,然後瞬間變成餓狼,顧不得嘴角早已垂涎三尺的口水,撲向無辜的小兔子……

那一晚,大家都流淚了,不是因為要分別,也不是因為學到了新的緩動技能,而是那外焦里嫩的口感,彌而不散的香味實在讓人慾罷不能。每吃一口,味蕾都要接受一浪又一浪不能自已的挑撥,感覺就像自己在雲霧中漫遊,天地間徜徉,實在好吃到哭,感動到淚。

PS: 本故事世界觀和背景可參見 GitHub

更多分享,請關注微信公眾號 YFE:

推薦閱讀:

前端日刊-2018.02.25
原生JS前後端同構
前端日刊-2018.02.23
序 · 關於專欄
[翻譯] Makefile - 失落的藝術

TAG:動畫 | 前端開發 | 演算法 |