第一次在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次方曲线)
- 滥用硬件加速也会导致性能问题