第一次在segmentfault上發文章 :),迎接批評斧正。原文最初宣布在 https://github.com/WarpPrism/…
動畫相干觀點
- 幀:動畫過程當中每個靜止的狀況,每一張靜止的圖片
- 幀率:革新頻次,每秒鐘播放的幀數,FPS(frame per second),單元是Hz
- 幀時長:每一幀住手的時候,如60FPS的動畫幀時長約為16.7ms,意味着瀏覽器必須在16.7ms內繪製完這一幀
- 硬件加速:硬件有三個處理器:CPU、GPU和APU(聲響處理器)。他們經由過程PCI/AGP/PCIE總線交流數據。GPU在浮點運算、并行盤算等部份盤算方面,顯著高於CPU的機能。硬件加速即應用GPU舉行動畫的盤算
- 緩動:最平常的動畫就是勻速的動畫,每次增添牢固的值。緩動就是用來修正每次增添的值,讓其根據不規律的體式格局增添,完成動畫的變化。
- 瀏覽器的革新率:平常為60Hz
前端動畫分類
從掌握角度分,前端動畫分為兩種:
- JavaScript掌握的動畫
- CSS掌握的動畫
JS動畫
JS動畫的道理是經由過程setTimeout
setInterval
或requestAnimationFrame
要領繪製動畫幀(render),從而動態地轉變網頁中圖形的顯現屬性(如DOM款式,canvas位圖數據,SVG對象屬性等),進而到達動畫的目標。
多半情況下,應 起首選用 requestAnimationFrame
要領(RAF),由於RAF的道理是會在瀏覽器下一次重繪之前更新動畫,即它的革新頻次和瀏覽器本身的革新頻次保持一致(平常為60Hz),從而確保了機能。別的RAF在瀏覽器切入背景時會停息實行,也能夠提拔機能和電池壽命。(來自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 完成幀動畫的一個例子,能夠幫你更好的明白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對象數組
* @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 // 住手輪迴播放
}
_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)
}
/**
* 遞次輪迴播放
* @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)
}
// 倒序輪迴播放
reverseLoopPlay (frameSections = this.frames) {
frameSections.reverse()
this.loopPlay(frameSections)
}
// 鞦韆式(單擺式)輪迴播放:即從第一幀播放到末了一幀,再由末了一幀播放到第一幀,云云輪迴
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 FrameAnim
CSS3 動畫
css動畫的道理是經由過程transition
屬性或@keyframes/animation
定義元素在動畫中的關鍵幀,以完成漸變式的過渡。
css動畫有以下特性:
長處
- CSS動畫完成比較簡單
- CSS動畫實行與JS主線程無關,例如在Chromium里,css動畫運行在compositor thread線程中,縱然你js線程卡住,css動畫照舊實行
- 強迫運用硬件加速,能有用應用GPU
瑕玷
- 只能操縱DOM或XML對象的部份屬性
- 動畫掌握能力柔弱,不能逐幀定義動畫狀況
- 支撐的緩動函數有限(CSS3動畫的貝塞爾曲線是一個規範3次方曲線)
- 濫用硬件加速也會致使機能題目
<!– 能夠經由過程以下代碼強迫開啟硬件加速:
.anim-cube {
z-index: 9999; // 指定z-index,盡量高
}
.anim-cube {
transform: translateZ(0);
/* 或 */
transform: translate3d(0, 0, 0);
}
前端動畫卡頓的緣由
丟幀:瀏覽器繪製某一幀的時長凌駕了均勻時長(幀超時),為了完成全部動畫不能不拋棄背面的動畫幀,形成丟幀徵象。畫面就湧現了所謂的閃灼,卡頓。
致使幀超時的緣由有許多,最主要的緣由是layout、paint帶來的機能開支:
無論是JS動畫,照樣CSS動畫,在操縱元素的某些款式(如height,width,margin,padding),會觸發layout和paint,如許每一幀就會發生龐大的機能開支,相反,運用transform屬性則不會,詳細哪些屬機能觸發能夠參考CSS Trigers,總之,我們應盡量運用影響小的屬性(transform,opacity)來做動畫。
假如採納的是基於圖片切換的幀動畫手藝,請確保一切圖片預加載終了,且用cacheImgs數組緩存一切圖片資本到內存中,不然也會湧現卡頓徵象。
layout: 瀏覽器會對這些元素舉行定位和規劃,這一步也叫做reflow或許layout。
paint: 瀏覽器繪製這些元素的款式,色彩,背景,大小及邊框等,這一步也叫做repaint。
composite: 然後瀏覽器會將各層的信息發送給GPU,GPU會將各層合成;顯現在屏幕上。
怎樣挑選最合適的動畫手藝
跟着當代web手藝的生長,無論是CSS動畫照樣JS動畫,機能瓶頸越來越小,我們只需挑選合適營業須要的手藝,一樣能創作出絲滑般順暢的web動畫。假如着實沒法挑選,看下圖(僅供參考):
平常來說,動畫機能好壞以下所示:
JS+Canvas > CSS + DOM > JS + DOM
這裡是一個動畫手藝比較的Codepen Demo
動畫緩動函數
- Linear:無緩動結果
- Quadratic:二次方的緩動(t^2)
- Cubic:三次方的緩動(t^3)
- Quartic:四次方的緩動(t^4)
- Quintic:五次方的緩動(t^5)
- Sinusoidal:正弦曲線的緩動(sin(t))
- Exponential:指數曲線的緩動(2^t)
- Circular:圓形曲線的緩動(sqrt(1-t^2))
- Elastic:指數衰減的正弦曲線緩動
- 凌駕局限的三次方緩動((s+1)t^3 – st^2)
- 指數衰減的反彈緩動
緩動函數的完成可參考Tween.js
前端畫圖手藝 VS 前端動畫手藝
前端畫圖手藝平常指以HTML5為代表的(canvas,svg,webgl等)2D、3D圖形繪製手藝。它和前端動畫之間沒有包括與被包括的關聯,更不能將它們 等量齊觀,只要二者的有機連繫才創建出炫酷的UI界面。