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