摘要:它和前端動畫之間沒有包含與被包含的關系,更不能將它們混為一談,只有兩者的有機結合才能創(chuàng)建出炫酷的界面。
第一次在segmentfault上發(fā)文章 :),歡迎評論指正。原文最初發(fā)表在 https://github.com/WarpPrism/...
動畫相關概念幀:動畫過程中每一個靜止的狀態(tài),每一張靜止的圖片
幀率:刷新頻率,每秒鐘播放的幀數(shù),F(xiàn)PS(frame per second),單位是Hz
幀時長:每一幀停留的時間,如60FPS的動畫幀時長約為16.7ms,意味著瀏覽器必須在16.7ms內繪制完這一幀
硬件加速:硬件有三個處理器:CPU、GPU和APU(聲音處理器)。他們通過PCI/AGP/PCIE總線交換數(shù)據(jù)。GPU在浮點運算、并行計算等部分計算方面,明顯高于CPU的性能。硬件加速即利用GPU進行動畫的計算
緩動:最普通的動畫就是勻速的動畫,每次增加固定的值。緩動就是用來修改每次增加的值,讓其按照不規(guī)律的方式增加,實現(xiàn)動畫的變化。
瀏覽器的刷新率:通常為60Hz
前端動畫分類從控制角度分,前端動畫分為兩種:
JavaScript控制的動畫
CSS控制的動畫
JS動畫JS動畫的原理是通過setTimeout setInterval 或requestAnimationFrame 方法繪制動畫幀(render),從而動態(tài)地改變網頁中圖形的顯示屬性(如DOM樣式,canvas位圖數(shù)據(jù),SVG對象屬性等),進而達到動畫的目的。
多數(shù)情況下,應 首先選用 requestAnimationFrame方法(RAF),因為RAF的原理是會在瀏覽器下一次重繪之前更新動畫,即它的刷新頻率和瀏覽器自身的刷新頻率保持一致(一般為60Hz),從而確保了性能。另外RAF在瀏覽器切入后臺時會暫停執(zhí)行,也可以提升性能和電池壽命。(來自MDN)
// requestAnimationFrame Demo let i = 0 let render = () { if (i >= frame.length) i = 0 let currentFrame = frame[i] drawFrame(currentFrame) i++ requestAnimationFrame(render) } requestAnimationFrame(render)
下面代碼是一個用js + canvas 實現(xiàn)幀動畫的一個例子,可以幫你更好的理解js動畫原理:
/** * 基于canvas的幀動畫庫 * 最近修改日期:2018-06-22 */ import { IsArray } from "Utils" class FrameAnim { constructor ({ frames, canvas, fps, useRAF }) { this._init({ frames, canvas, fps, useRAF }) } /** * 實例初始化 * @param options -> * @param {Array} frames image對象數(shù)組 * @param {Object} canvas canvas dom 對象 * @param {Number} fps 幀率 * @param {Boolean} useRAF 是否使用requestAnimationFrame方法 */ _init ({ frames, canvas, fps, useRAF }) { this.frames = [] if (IsArray(frames)) { this.frames = frames } this.canvas = canvas this.fps = fps || 60 this.useRAF = useRAF || false this.ctx = this.canvas.getContext("2d") // 繪圖上下文 this.cwidth = this.canvas.width this.cheight = this.canvas.height this.animTimer = null // 動畫定時器 this.currentIndex = 0 // 當前幀 this.stopLoop = false // 停止循環(huán)播放 } _play (frameSections, fromIndex = 0) { return new Promise((resolve, reject) => { this.currentIndex = fromIndex || 0 if (this.useRAF) { let render = () => { this.ctx.clearRect(0, 0, this.cwidth, this.cheight) let currentFrame = frameSections[this.currentIndex] this.ctx.drawImage(currentFrame, 0, 0, currentFrame.width, currentFrame.height) this.currentIndex++ if (this.currentIndex <= frameSections.length - 1) { requestAnimationFrame(render) } else { this._stopPlay() resolve({finish: true}) } } this.animTimer = requestAnimationFrame(render) } else { this.animTimer = setInterval(() => { if (this.currentIndex > frameSections.length - 1) { this._stopPlay() resolve({finish: true}) return } this.ctx.clearRect(0, 0, this.cwidth, this.cheight) let currentFrame = frameSections[this.currentIndex] this.ctx.drawImage(currentFrame, 0, 0, currentFrame.width, currentFrame.height) this.currentIndex++ }, 1000 / this.fps) } }) } _stopPlay () { if (this.useRAF) { cancelAnimationFrame(this.animTimer) this.animTimer = null } else { clearInterval(this.animTimer) this.animTimer = null } } stopAllFrameAnimation () { this.stopLoop = true this._stopPlay() } /** * 順序播放 * @param {Array} frameSections 動畫幀片段 */ linearPlay (frameSections = this.frames) { return this._play(frameSections, this.currentIndex) } /** * 順序循環(huán)播放 * @param {Array} frameSections 動畫幀片段 */ loopPlay (frameSections = this.frames) { this._play(frameSections, this.currentIndex).then((res) => { if (!this.stopLoop) { this.currentIndex = 0 this.loopPlay(frameSections, this.currentIndex) } }) } // 倒序播放 reversePlay (frameSections = this.frames) { frameSections.reverse() return this.linearPlay(frameSections) } // 倒序循環(huán)播放 reverseLoopPlay (frameSections = this.frames) { frameSections.reverse() this.loopPlay(frameSections) } // 秋千式(單擺式)循環(huán)播放:即從第一幀播放到最后一幀,再由最后一幀播放到第一幀,如此循環(huán) swingLoopPlay (frameSections = this.frames) { this._play(frameSections, this.currentIndex).then((res) => { if (!this.stopLoop) { this.currentIndex = 0 frameSections.reverse() this.swingLoopPlay(frameSections) } }) } /** * 銷毀資源,需謹慎使用 */ disposeResource () { this.stopAllFrameAnimation() for (let i = 0; i < this.frames.length; i++) { this.frames[i] = null } this.frames = null this.canvas = this.ctx = null } } export default FrameAnimCSS3 動畫
css動畫的原理是通過transition屬性或@keyframes/animation定義元素在動畫中的關鍵幀,以實現(xiàn)漸變式的過渡。
css動畫有以下特點:
優(yōu)點
CSS動畫實現(xiàn)比較簡單
CSS動畫執(zhí)行與JS主線程無關,例如在Chromium里,css動畫運行在compositor thread線程中,即使你js線程卡住,css動畫照常執(zhí)行
強制使用硬件加速,能有效利用GPU
缺點
只能操作DOM或XML對象的部分屬性
動畫控制能力薄弱,不能逐幀定義動畫狀態(tài)
支持的緩動函數(shù)有限(CSS3動畫的貝塞爾曲線是一個標準3次方曲線)
濫用硬件加速也會導致性能問題