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