摘要:直到年,世界上第一部動畫片滑稽臉的幽默相問世。上一次視神經傳遞的圖像將會在大腦中存留,直到下一次神經信號到達。移動設備還是相當慘烈,并沒有開始支持。市面上有很多動畫庫,大家可以開箱即用。有一些是針對操作的,也有一些是針對對象。
背景
138.2億年前,世界上沒有時間和空間,或許世界都不存在,在一個似有似無的點上,匯集了所有的物質,它孕育著無限的能量與可能性。
宇宙大爆炸巨大的內力已無法被抑制,瞬間爆發,它爆炸了!世界上有了時間和空間,隨著歲月的變遷,時光的流逝,無數的星系、恒星、衛星、彗星形成。我們生活的地球,只是茫茫宇宙中的一個小小的天體,或許在遙遠的宇宙的另一邊,會有平行世界的存在,或許在那里,我們可能是醫生、老師、公務員。科學家說我們的宇宙正在加速度的膨脹,暗能量在無限吞噬著暗物質,未來的世界將會變得虛無縹緲。
人類起源宇宙的形成,帶來了無限可能性,人類釋放著欲望和克制,對宇宙的渴望產生于公元前五世紀,古巴比倫人通過觀察天體的位置以及外觀變化,來預測人世間的各種事物。在遙遠的古羅馬,人們也舞弄著靈魂,把不羈的想象賦予肉體。Anim,來源于拉丁語,代表著靈魂與生命,代表著所有與生俱來。似乎世間萬物都存在聯系,宇宙、自然都存在靈魂。
動畫的形成兩萬五前年錢的石器時代,石洞中的野獸奔跑分析圖,這是人類開始試圖捕捉動作最早證據。文藝復興時期的達芬奇畫作上,用兩只手臂兩條腿來標識上下擺動的動作,在一張畫作上做出不同時間的兩個動作。
直到1906年,世界上第一部動畫片《滑稽臉的幽默相》問世。
所以動畫是否就是將多個畫面連起來播放呢?
時間是連續的嗎?是可以無線分割的嗎?我也不太清楚,你看到的流星、人們的動作是連續的嗎?或許是吧,畢竟現實生活中還沒有像瞬間移動這種事情發生吧。
神經可能不是連續的,生物課學過,神經的傳遞是一個電信號傳遞過程,并且是顆粒的(神經信號),那么我們看到的東西在我們腦海里的成像一定不是連續的。
那么我們為什么能看到連續的動作呢?
視覺暫留(Persistence of vision),讓我們看到了連續的畫面,視神經反應速度大約為1/16秒,每個人不太一樣,有些人高一點,一些人低一點。上一次視神經傳遞的圖像將會在大腦中存留,直到下一次神經信號到達。維基百科上說,日常用的日光燈每秒鐘大約會熄滅100次,但是你并沒有感覺。
一般電影的在幀率在24FPS以上,一般30FPS以上大腦會認為是連貫的,我們玩的游戲一般在30FPS,高幀率是60FPS。
小時候一定看過翻頁動畫吧,可以看一看翻頁動畫-地球進化史
前端動畫實現Atwood 定律:Any application that can be written in JavaScript will eventually be written in JavaScript.
前端做動畫不是什么新鮮事了,從jQuery時代,到當下,無不是前端動畫橫行的時代。
我們知道多張不同的圖像連在一起就變成了動態的圖像。
在前端的世界里,瀏覽器在視覺暫留時間內,連續不斷的逐幀輸出圖像。每一幀輸出一張圖像。
提及動畫一定會討論到幀率(FPS, Frame Per Second),代表每秒輸出幀數,也就是瀏覽器每秒展示出多少張靜態的圖像。
DOM動畫中的 CSS3CSS3 動畫是當今盛行的 Web 端制作動畫的方式之一,對于移動設備來說覆蓋率已經非常廣泛,在日常開發中可以使用。CSS3 動畫只能通過對 CSS 樣式的改變控制 DOM 進行動畫
CSS3 Animation MDN
CSS3 Translate MDN
DOM動畫中的 WebAnimationWebAnimation 還在草案階段,在Chrome可以嘗試使用一下。移動設備還是相當慘烈,iOS 并沒有開始支持。
Element Animation MDN
CSS3 和 WebAnimation 都只能作用于DOM,那么,如果我們想讓 Canvas 上的對象產生動畫,那我們該怎么辦呢?
JavaScript既然我們知道動畫的原理,其實就是讓用戶看到連續的圖片,并且每一張圖片是有變化的。
對于事物來講,我們可以通過改變某些數值來修改他的屬性,從來改變他的外在展示。比如正方形的邊長,顏色的RGB值,臺風的位置(世界坐標),在每一幀去改變這些數值,根據這些數值將對象繪依次制到屏幕上,將會產生動畫。
通過上面的描述,我們知道,實現一個動畫,其實是數值隨時間變化,以幀為時間單位。
在很久很久以前,JavaScript 使用 setInterval 進行定時調用函數。所以可以使用setInterval來進行數值的改變。
為了更好的讓各位前端小哥哥小姐姐們做動畫,出現了requestAnimationFrame,requestAnimationFrame 接收一個函數,這個函數將在下一幀渲染之前執行,也就是說,不需要太多次的計算,只要在下一幀渲染之前,我們將需要修改的數值修改掉即可。requestAnimationFrame 的幀率和硬件以及瀏覽器有關,一般是60FPS(16.66666666ms/幀)。
我們利用 Dom 進行動畫的演示~
元素移動創建一個方塊
設置寬高和背景顏色
.box { width: 100px; height: 100px; background: red; }
const box = document.querySelector(".box") // 獲取方塊元素 let value = 0 // 設置初始值 // 創建每一幀渲染之前要執行的方法 const add = () => { requestAnimationFrame(add) // 下一幀渲染之前繼續執行 add 方法 value += 5 // 每幀加數值增加5 box.style.transform = `translateX(${value}px)` // 將數值設置給 方塊 的 css 屬性 transform 屬性可以控制元素在水平方向上的位移 } requestAnimationFrame(add) // 下一幀渲染之前執行 add 方法
這樣,方塊每幀向右移動 5 像素,每秒移動60*5=300像素,不是每秒跳動一下,而是一秒在300像素內均勻移動哦。
補間動畫上一個demo實現了小方塊從左到右的移動,但是貌似他會永無止境的移動下去,直到數值溢出,小時候學過flash的朋友都知道補間動畫,其實就是讓小方塊0px到300px平滑移動。其實就是固定的時間點,有固定的位置。
所以我們只需要根據運動的已過時間的百分比去計算數值。
保持之前的 HTML 和 CSS 不變
/** * 執行補間動畫方法 * * @param {Number} start 開始數值 * @param {Number} end 結束數值 * @param {Number} time 補間時間 * @param {Function} callback 每幀的回調函數 */ function animate(start, end, time, callback) { let startTime = performance.now() // 設置開始的時間戳 let differ = end - start // 拿到數值差值 // 創建每幀之前要執行的函數 function loop() { raf = requestAnimationFrame(loop) // 下一陣調用每幀之前要執行的函數 const passTime = performance.now() - startTime // 獲取當前時間和開始時間差 let per = passTime / time // 計算當前已過百分比 if (per >= 1) { // 判讀如果已經執行 per = 1 // 設置為最后的狀態 cancelAnimationFrame(raf) // 停掉動畫 } const pass = differ * per // 通過已過時間百分比*開始結束數值差得出當前的數值 callback(pass) // 調用回調函數,把數值傳遞進去 } let raf = requestAnimationFrame(loop) // 下一陣調用每幀之前要執行的函數 }
我們調用一下補間動畫,讓數值經過1秒勻速從0變成400。
let box = document.querySelector() animate(0, 400, 1000, value => { box.style.transform = `translateX(${value}px)` // 將數值設置給 方塊 的 css 屬性 transform 屬性可以控制元素在水平方向上的位移 })
一個簡單的勻速補間動畫就這么被我們做好了。
非勻速動畫那萬一,這個動畫不是非勻速的,比如抖一抖啊,彈一彈,那該怎么辦呢?
當然也是一樣,根據已過時間的百分比去計算數值
時間是勻速的,但是數值不是,如果數值變化是有規律的,那么我們就可以使用時間來表示數值,創建一個接收時間比例(當前時間百分比),返回當前位置比例(當前位置百分比)的方法。
我們稱這個方法叫做緩動方法。
如果速度從慢到快,我們可以把時間和數值的圖像模擬成以下的樣子。
公式為 rate = time ^ 2
對應的函數應該是
function easeIn(time) { // 接收一個當前的時間占總時間的百分比比 return time ** 2 }
這個實現加速后抖動結束的效果,在Time小于0.6時是一個公式,time大于0.6是另外一個公式。
Time < 0.6 時: Rate = Time / 0.6 ^ 2
Time > 0.6 時: Rate = Math.sin((Time-0.6) ((3 Math.PI) / 0.4)) * 0.2 + 1
最終實現的函數是
function shake(time) { if (time < 0.6) { return (time / 0.6) ** 2 } else { return Math.sin((time-0.6) * ((3 * Math.PI) / 0.4)) * 0.2 + 1 } }
我們改造一下之前的 animate 函數,接收一個 easing 方法。
/** * 執行補間動畫方法 * * @param {Number} start 開始數值 * @param {Number} end 結束數值 * @param {Number} time 補間時間 * @param {Function} callback 每幀回調 * @param {Function} easing 緩動方法,默認勻速 */ function animate(start, end, time, callback, easing = t => t) { let startTime = performance.now() // 設置開始的時間戳 let differ = end - start // 拿到數值差值 // 創建每幀之前要執行的函數 function loop() { raf = requestAnimationFrame(loop) // 下一陣調用每幀之前要執行的函數 const passTime = performance.now() - startTime // 獲取當前時間和開始時間差 let per = passTime / time // 計算當前已過百分比 if (per >= 1) { // 判讀如果已經執行 per = 1 // 設置為最后的狀態 cancelAnimationFrame(raf) // 停掉動畫 } const pass = differ * easing(per) // 通過已過時間百分比*開始結束數值差得出當前的數值 callback(pass) } let raf = requestAnimationFrame(loop) // 下一陣調用每幀之前要執行的函數 }
測試一下,將我們剛剛創建的 easing 方法傳進來
加速運動
let box = document.querySelector(".box") animate(0, 500, 400, value => { box.style.transform = `translateX(${value}px)` // 將數值設置給 方塊 的 css 屬性 transform 屬性可以控制元素在水平方向上的位移 }, easeIn)
加速后抖一抖
let box = document.querySelector(".box") animate(0, 500, 400, value => { box.style.transform = `translateX(${value}px)` // 將數值設置給 方塊 的 css 屬性 transform 屬性可以控制元素在水平方向上的位移 }, shake)總結
這些只是 JavaScript 動畫基礎中的基礎,理解動畫的原理后,再做動畫就更得心應手了。
市面上有很多JS動畫庫,大家可以開箱即用。有一些是針對DOM操作的,也有一些是針對 JavaScript 對象。實現原理你都已經懂了。
上述代碼已發布到Github: https://github.com/fanmingfei/animation-base
我的另外兩篇關于動畫的文章:
為何 Canvas 內元素動畫總是在顫抖?
前端動畫/游戲開發 requestAnimationFrame 之 鎖幀
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98154.html
摘要:前言相信做前端的朋友沒有不知道的,都知曉新增了不少新的特性,但是你知道是怎么來的嗎今天就讓閏土來帶大家大話的前世今生。之前可能是自己娛樂為主,大家來旁觀為輔。還有一個比較大的版本就是,它是年正式誕生的。大話前端系列文章較長,未完待續。 showImg(https://segmentfault.com/img/bV4pck?w=1280&h=693); 前言 相信做前端的朋友沒有不知道E...
摘要:由此可知閉包是函數的執行環境以及執行環境中的函數組合而構成的。此時產生了閉包。二閉包的作用閉包的特點是讀取函數內部局部變量,并將局部變量保存在內存,延長其生命周期。三閉包的問題使用閉包會將局部變量保持在內存中,所以會占用大量內存,影響性能。 一、什么是閉包 1.閉包的定義 閉包是一種特殊的對象。它由兩部分構成:函數,以及創建該函數的環境(包含自由變量)。環境由閉包創建時在作用域中的任何...
摘要:前端日報精選入門指南入口,輸出,加載器和插件中數據類型轉換讓我印象深刻的面試題大話大前端時代一與的組件化庖丁解牛一發布中文第期手把手教你用管理狀態上個快速編程技巧眾成翻譯中執行順序組件解耦之道眾成翻譯組件模型啟示錄有個梨作 2017-07-10 前端日報 精選 Webpack入門指南: 入口,輸出,加載器和插件JavaScript中數據類型轉換讓我印象深刻的javascript面試題大...
摘要:腳本執行,事件處理等。引擎線程,也稱為內核,負責處理腳本程序,例如引擎。事件觸發線程,用來控制事件循環可以理解為,引擎線程自己都忙不過來,需要瀏覽器另開線程協助。異步請求線程,也就是發出請求后,接收響應檢測狀態變更等都是這個線程管理的。 一、進程與線程 現代操作系統比如Mac OS X,UNIX,Linux,Windows等,都是支持多任務的操作系統。 什么叫多任務呢?簡單地說,就是操...
摘要:在中,通過棧的存取方式來管理執行上下文,我們可稱其為執行棧,或函數調用棧。因為執行中最先進入全局環境,所以處于棧底的永遠是全局環境的執行上下文。 一、什么是執行上下文? 執行上下文(Execution Context): 函數執行前進行的準備工作(也稱執行上下文環境) JavaScript在執行一個代碼段之前,即解析(預處理)階段,會先進行一些準備工作,例如掃描JS中var定義的變量、...
閱讀 1239·2021-11-11 16:55
閱讀 1537·2021-10-08 10:16
閱讀 1188·2021-09-26 10:20
閱讀 3569·2021-09-01 10:47
閱讀 2451·2019-08-30 15:52
閱讀 2682·2019-08-30 13:18
閱讀 3194·2019-08-30 13:15
閱讀 1115·2019-08-30 10:55