藉助JavaScript,實現網頁動畫效果html
簡易Demo:es/js_animation_test.html前端
Anikyu源代碼倉庫:anikyugit
本人不才,大學期間搞過各類各樣的設計類軟件,包括作動畫的Flash、After Effects、Premiere,作3D的3ds Max、Cinema 4D;終於,在大學的第三年,專業開了網頁設計課(大二時其實我也有水過一個作網頁的校選修),我又不慎接觸了作網頁的Dreamweaver。Adobe全家桶齊了。程序員
要學作網頁,就得學HTML;要把網頁寫好看,就得學CSS;要讓網頁有點生氣,就得學JavaScript;這不就蹚入了前端這潭渾水。github
// 其實本人並不承認本身是程序員,最可能是會寫網頁的設計師數據庫
大學期間,本人學的是數字媒體技術專業,計算機學院裏的專業,C、C++、數據結構、數據庫概論、計算機圖形學等課程都有上過,外加遊戲原畫、Flash、三維設計一類的偏向設計類的課程,而後本身又稍稍接觸了一點點PHP。曾經我想,要不將來作UI/視頻/3D設計師吧;但最終找工做的時候,認真思考了一下,專業課設計類課程彷佛支持不了個人一些野心,外加我比較菜,大學期間全部軟件都接觸很淺;到後來一直不知道3D模型貼圖咋貼,就試着經過學Three.js來學3D。因此就暫時先試着用Dreamweaver這個技能來找工做吧。npm
至於設計,我認爲總有一天我會回來的~segmentfault
本人認爲,網頁與傳統動畫同樣,都是可以使人產生愉悅的能夠用於觀賞的產品;但比傳統動畫更強大的是,網頁可以與用戶直接產生交互。瀏覽器
專業動畫設計軟件裏,動畫的實現通常是逐幀動畫或是關鍵幀動畫。數據結構
創做者每一幀繪製一次,最終將多幀連續播放,實現動畫。
創做者在每一個關鍵點繪製一幀,最終由程序在兩個關鍵幀之間進行插值,獲得中間幀。插值能夠是線性插值,也能夠是包含緩動的插值。
對於網頁動畫,在早期,若動畫效果很複雜,甚是精美,通常都直接使用Flash等插件來製做;簡單的動畫則使用JavaScript中的setInterval()來實現,即每隔若干毫秒(保持性能的同時不讓用戶感到卡頓)對某一對象某一數值進行一次增長,請求一次動畫幀,直到該對象該值的量到達預想值,由此實現動畫。jQuery中的animate()對其進行了封裝。同時還有SVG動畫,使用animate標籤來進行實現。隨着CSS3的出現,DOM元素動畫/過渡效果可使用CSS實現。
固然,CSS動畫的使用也有一些侷限性:
最近在學習Three.js,其核心庫彷佛不包含動畫。其官方示例有使用Tween.js這個動畫庫,但因爲我比較想試着本身實現一個相似的動畫庫(實際上是由於它的用法和個人想法不太相符)。
所以,我本身封裝了一個基於JavaScript實現的動畫庫 —— Anikyu 。
固然本文先並不着重介紹我所封裝的庫。對於其使用方法,請直接看anikyu的倉庫。
咱們都知道,網頁有兩個維度:寬度和高度。
若是要用到動畫,那就再加個時間。
(好吧,若是非要說WebGL或是CSS 3D等等三維技術,那再加個深度;此處不討論)。
要讓一個對象會動,就必須引入時間這個概念;由於時間點是一個點,其中包含的是對象在在當前時間點的狀態;既然要讓對象會動,就得找下一個時間點,最終連點成線,就產生了一個時間軸,這樣就有了時間這個維度。
如今咱們把兩個時間點之間的變化過程當作一個總體,開始時間點動畫進度爲0,結束時間點動畫進度爲1。
設開始時間爲64,結束時間爲1024,當前時間爲256,那麼:
總時間差 = 結束時間 - 開始時間 = 1024 - 64 = 960
當前動畫進度 = (當前時間 - 開始時間)/總時間差 = (256 - 64)/960 = 0.2 ;
設開始值爲222,結束值爲666,那麼:
當前時間點(相對於開始值)的增量
= (結束值 - 開始值)*當前動畫進度
= (666 - 222)*0.2
= 88.8
所以在該時間點下:
當前對象的值 = 開始值 + 當前時間的增量 = 222 + 88.8 = 310.8
以後使用定時器執行上方的步驟,每次時間改變,從新計算一次進度和時間點增量,並賦值給目標對象,直到當前動畫進度達到1,此時動畫完成。
在上面的代碼中,咱們已經實現了最簡單的補間,即勻速補間。但在不少狀況下,僅有勻速補間是不夠的,例如咱們可能要實現一個動畫運行先快(慢)後慢(快)/中間快(慢)兩頭慢(快)/稍微出去一點再回來/彈跳等諸如此類的小效果。在After Effects、Cinema 4D等設計類軟件時間軸面板上,咱們能夠對緩動函數進行可視化的編輯,從而咱們能夠直觀地看到緩動函數的曲線。
與此相似,藉助CSS3所提供的transition-timing-function屬性,咱們也能夠直接在CSS中實現緩動動畫,其屬性值包括:linear、ease、ease-in、ease-out、ease-in-out,外加這五個屬性值所基於的cubic-bezier(n,n,n,n) —— 三次貝塞爾曲線。
CSS3 所提供的緩動函數已經基本知足咱們平常的一些動畫需求,即上文所說起的先快(慢)後慢(快)/中間快(慢)兩頭慢(快)/稍微出去一點再回來;但對於彈跳效果,因爲CSS3 緩動動畫僅支持兩點之間的簡單緩動,而彈跳效果在動畫過程當中沒法使用CSS3 所支持的三次貝塞爾曲線來表達(彈跳動畫包含多個轉折點),所以沒法使用純CSS來實現彈跳。
但在JavaScript中,咱們能夠本身編寫緩動函數。
緩動函數參見 ECharts 示例頁面
In | Out | InOut |
---|---|---|
quadraticIn | quadraticOut | quadraticInOut |
cubicIn | cubicOut | cubicInOut |
quarticIn | quarticOut | quarticInOut |
quinticIn | quinticOut | quinticInOut |
sinusoidalIn | sinusoidalOut | sinusoidalInOut |
exponentialIn | exponentialOut | exponentialInOut |
circularIn | circularOut | circularInOut |
elasticIn | elasticOut | elasticInOut |
backIn | backOut | backInOut |
bounceIn | bounceOut | bounceInOut |
函數曲線示例:
若是自行百度,能夠發現這些補間函數基本都是大同小異的。它們都接受一個值 當前動畫播放實際進度k,返回一個通過處理的進度值 k2 用以供計算函數算出當前時間點的變化量。
假設當前動畫狀態以及所給定值和上一步同樣,緩動函數使用的是bounceIn,則:
當前時間點(相對於開始值)的增量
= (結束值 - 開始值) * 緩動函數(當前動畫進度)
= (666 - 222)*bounceOut(0.2)
= 134.31
所以在該時間點下:
當前對象的值 = 開始值 + 當前時間的增量 = 222 + 134.31 = 356.31
正如上文所說起,動畫的存在依賴於時間這個維度。一個時間點表示一個時間點的狀態,那如何獲得動畫播放期間每個時間點的狀態呢?
答案就是定時器。定時器可讓瀏覽器每間隔一段時間來執行一段函數。在早期瀏覽器(IE9或更低版本)中僅可以使用setInterval()來實現不斷地請求動畫,新版本瀏覽器中則引入了專門用於建立動畫的requestAnimationFrame()。
requestAnimationFrame與setInterval區別在於:
Anikyu包含了requestAnimationFrame的 Polyfill,以用於支持IE9瀏覽器。
經過定時器不斷執行下列操做:
獲取到當前時間 - 將當前時間和開始時間進行一系列計算獲得當前時間點狀態 - 將狀態賦值給原始對象
便可實現動畫。
function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); }
clamp函數用於鉗制數值範圍。因爲當前動畫進度的值一定是一個[0,1]區間的值,所以咱們必須讓動畫進度限制在該範圍內,不然可能會致使最終效果值小於/大於預期值
function bounceOut(k) { if (k < (1 / 2.75)) { return 7.5625 * k * k; } else if (k < (2 / 2.75)) { return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; } else if (k < (2.5 / 2.75)) { return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; } else { return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; } }
bounceOut函數是上文所說起的30個緩動函數之一,可在動畫進度快要結束時產生彈回的效果。將當前動畫進度傳入後,能夠得到一個通過處理的進度,在實際計算時根據該進度獲得當前時間點狀態,實現變速運動。若當前動畫進度不通過該函數處理,則動畫爲勻速運動。
let el = document.getElementById('el') let init = 222; let end = 666; let timeDelta = 960; let startTime = Date.now()
function animate() { interval = requestAnimationFrame(animate) let loop = () => { let currentProgress = (Date.now() - startTime) / timeDelta currentProgress = clamp(currentProgress, 0, 1) let sumNumber = (end - init) * bounceOut(currentProgress) // let sumNumber = (end - init) * currentProgress let currentStatus = init + sumNumber if (currentProgress === 1) { cancelAnimationFrame(interval) el.style.transform = `translate(${end}px)` } else { el.style.transform = `translate(${currentStatus}px)` } } loop() } animate()
以上就是我對JavaScript動畫的理解。藉此機會,我也封裝了一個上文所說起的動畫庫,Anikyu。
Anikyu是本人春節期間在家,正好也是疫情期間,作這個動畫demo時忽然想封裝的一個庫。正巧藉此機會也學習了一下ES6沒用過的特性、Webpack、Babel,同時也將該庫發佈到了 npm 。
各位要是以爲靠譜不妨來用一下,支持IE 9+瀏覽器,支持Node.js環境。
我也嘗試將Anikyu代碼結構、開發過程寫一篇新的博客:
嘗試經過封裝一個庫來學習JavaScript(ES6)相關特性以及相關構建工具]
(#7)
可能有點多,我想起來的時候慢慢補充。