Open WarpPrism opened 6 years ago
从控制角度分,前端动画分为两种:
从实现方式上分:
JS动画的原理是通过setTimeout setInterval 或requestAnimationFrame 方法绘制动画帧(render),从而动态地改变网页中图形的显示属性(如DOM样式,canvas位图数据,SVG对象属性等),进而达到动画的目的。
setTimeout
setInterval
requestAnimationFrame
多数情况下,应 首先选用 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 // 如果使用RAF,则不需要传入FPS,否则可用FPS控制帧率 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
css动画的原理是通过transition属性或@keyframes/animation定义元素在动画中的关键帧,以实现渐变式的过渡。
transition
@keyframes/animation
css动画有以下特点:
优点
缺点
丢帧:浏览器绘制某一帧的时长超过了平均时长(帧超时),为了完成整个动画不得不丢弃后面的动画帧,造成丢帧现象。画面就出现了所谓的闪烁,卡顿。
导致帧超时的原因有很多,最主要的原因是layout、paint带来的性能开销:
无论是JS动画,还是CSS动画,在操作元素的某些样式(如height,width,margin,padding),会触发layout和paint,这样每一帧就会产生巨大的性能开销,相反,使用transform属性则不会,具体哪些属性能触发可以参考CSS Trigers,总之,我们应尽可能使用影响小的属性(transform,opacity)来做动画。
如果采用的是基于图片切换的帧动画技术,请确保所有图片预加载完毕,且用cacheImgs数组缓存所有图片资源到内存中,否则也会出现卡顿现象。
layout: 浏览器会对这些元素进行定位和布局,这一步也叫做reflow或者layout。 paint: 浏览器绘制这些元素的样式,颜色,背景,大小及边框等,这一步也叫做repaint。 composite: 然后浏览器会将各层的信息发送给GPU,GPU会将各层合成;显示在屏幕上。
layout: 浏览器会对这些元素进行定位和布局,这一步也叫做reflow或者layout。
paint: 浏览器绘制这些元素的样式,颜色,背景,大小及边框等,这一步也叫做repaint。
composite: 然后浏览器会将各层的信息发送给GPU,GPU会将各层合成;显示在屏幕上。
随着现代web技术的发展,无论是CSS动画还是JS动画,性能瓶颈越来越小,我们只要选择适合业务需要的技术,一样能创作出丝滑般顺畅的web动画。如果实在无法选择,看下图(仅供参考):
一般来说,动画性能优劣如下所示:
JS+Canvas > CSS + DOM > JS + DOM
这里是一个动画技术比较的Codepen Demo
缓动函数的实现可参考Tween.js
前端绘图技术通常指以HTML5为代表的(canvas,svg,webgl等)2D、3D图形绘制技术。它和前端动画之间没有包含与被包含的关系,更不能将它们 混为一谈,只有两者的有机结合才能创建出炫酷的UI界面。
深入浏览器理解CSS animations 和 transitions的性能问题
前端性能优化之更平滑的动画
CSS vs JS动画:谁更快?
一篇文章说清浏览器解析和CSS(GPU)动画优化
动画相关概念
前端动画分类
从控制角度分,前端动画分为两种:
从实现方式上分:
JS动画
JS动画的原理是通过
setTimeout
setInterval
或requestAnimationFrame
方法绘制动画帧(render),从而动态地改变网页中图形的显示属性(如DOM样式,canvas位图数据,SVG对象属性等),进而达到动画的目的。多数情况下,应 首先选用
requestAnimationFrame
方法(RAF),因为RAF的原理是会在浏览器下一次重绘之前更新动画,即它的刷新频率和浏览器自身的刷新频率保持一致(一般为60Hz),从而确保了性能。另外RAF在浏览器切入后台时会暂停执行,也可以提升性能和电池寿命。(来自MDN)下面代码是一个用js + canvas 实现帧动画的一个例子,可以帮你更好的理解js动画原理:
CSS3 动画
css动画的原理是通过
transition
属性或@keyframes/animation
定义元素在动画中的关键帧,以实现渐变式的过渡。css动画有以下特点:
优点
缺点
前端动画卡顿的原因
丢帧:浏览器绘制某一帧的时长超过了平均时长(帧超时),为了完成整个动画不得不丢弃后面的动画帧,造成丢帧现象。画面就出现了所谓的闪烁,卡顿。
导致帧超时的原因有很多,最主要的原因是layout、paint带来的性能开销:
无论是JS动画,还是CSS动画,在操作元素的某些样式(如height,width,margin,padding),会触发layout和paint,这样每一帧就会产生巨大的性能开销,相反,使用transform属性则不会,具体哪些属性能触发可以参考CSS Trigers,总之,我们应尽可能使用影响小的属性(transform,opacity)来做动画。
如果采用的是基于图片切换的帧动画技术,请确保所有图片预加载完毕,且用cacheImgs数组缓存所有图片资源到内存中,否则也会出现卡顿现象。
如何选择最适合的动画技术
随着现代web技术的发展,无论是CSS动画还是JS动画,性能瓶颈越来越小,我们只要选择适合业务需要的技术,一样能创作出丝滑般顺畅的web动画。如果实在无法选择,看下图(仅供参考):
一般来说,动画性能优劣如下所示:
JS+Canvas > CSS + DOM > JS + DOM
这里是一个动画技术比较的Codepen Demo
动画缓动函数
缓动函数的实现可参考Tween.js
前端绘图技术 VS 前端动画技术
前端绘图技术通常指以HTML5为代表的(canvas,svg,webgl等)2D、3D图形绘制技术。它和前端动画之间没有包含与被包含的关系,更不能将它们 混为一谈,只有两者的有机结合才能创建出炫酷的UI界面。
参考链接
深入浏览器理解CSS animations 和 transitions的性能问题
前端性能优化之更平滑的动画
CSS vs JS动画:谁更快?
一篇文章说清浏览器解析和CSS(GPU)动画优化