资讯专栏INFORMATION COLUMN

前端动画技术的研究和比较

wushuiyong / 1057人阅读

摘要:它和前端动画之间没有包含与被包含的关系,更不能将它们混为一谈,只有两者的有机结合才能创建出炫酷的界面。

第一次在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 setIntervalrequestAnimationFrame 方法绘制动画帧(render),从而动态地改变网页中图形的显示属性(如DOM样式,canvas位图数据,SVG对象属性等),进而达到动画的目的。

多数情况下,应 首先选用 requestAnimationFrame方法(RAF),因为RAF的原理是会在浏览器下一次重绘之前更新动画,即它的刷新频率和浏览器自身的刷新频率保持一致(一般为60Hz),从而确保了性能。另外RAF在浏览器切入后台时会暂停执行,也可以提升性能和电池寿命。(来自MDN)

</>复制代码

  1. // requestAnimationFrame Demo
  2. let i = 0
  3. let render = () {
  4. if (i >= frame.length) i = 0
  5. let currentFrame = frame[i]
  6. drawFrame(currentFrame)
  7. i++
  8. requestAnimationFrame(render)
  9. }
  10. requestAnimationFrame(render)

下面代码是一个用js + canvas 实现帧动画的一个例子,可以帮你更好的理解js动画原理:

</>复制代码

  1. /**
  2. * 基于canvas的帧动画库
  3. * 最近修改日期:2018-06-22
  4. */
  5. import { IsArray } from "Utils"
  6. class FrameAnim {
  7. constructor ({ frames, canvas, fps, useRAF }) {
  8. this._init({ frames, canvas, fps, useRAF })
  9. }
  10. /**
  11. * 实例初始化
  12. * @param options ->
  13. * @param {Array} frames image对象数组
  14. * @param {Object} canvas canvas dom 对象
  15. * @param {Number} fps 帧率
  16. * @param {Boolean} useRAF 是否使用requestAnimationFrame方法
  17. */
  18. _init ({ frames, canvas, fps, useRAF }) {
  19. this.frames = []
  20. if (IsArray(frames)) {
  21. this.frames = frames
  22. }
  23. this.canvas = canvas
  24. this.fps = fps || 60
  25. this.useRAF = useRAF || false
  26. this.ctx = this.canvas.getContext("2d") // 绘图上下文
  27. this.cwidth = this.canvas.width
  28. this.cheight = this.canvas.height
  29. this.animTimer = null // 动画定时器
  30. this.currentIndex = 0 // 当前帧
  31. this.stopLoop = false // 停止循环播放
  32. }
  33. _play (frameSections, fromIndex = 0) {
  34. return new Promise((resolve, reject) => {
  35. this.currentIndex = fromIndex || 0
  36. if (this.useRAF) {
  37. let render = () => {
  38. this.ctx.clearRect(0, 0, this.cwidth, this.cheight)
  39. let currentFrame = frameSections[this.currentIndex]
  40. this.ctx.drawImage(currentFrame, 0, 0, currentFrame.width, currentFrame.height)
  41. this.currentIndex++
  42. if (this.currentIndex <= frameSections.length - 1) {
  43. requestAnimationFrame(render)
  44. } else {
  45. this._stopPlay()
  46. resolve({finish: true})
  47. }
  48. }
  49. this.animTimer = requestAnimationFrame(render)
  50. } else {
  51. this.animTimer = setInterval(() => {
  52. if (this.currentIndex > frameSections.length - 1) {
  53. this._stopPlay()
  54. resolve({finish: true})
  55. return
  56. }
  57. this.ctx.clearRect(0, 0, this.cwidth, this.cheight)
  58. let currentFrame = frameSections[this.currentIndex]
  59. this.ctx.drawImage(currentFrame, 0, 0, currentFrame.width, currentFrame.height)
  60. this.currentIndex++
  61. }, 1000 / this.fps)
  62. }
  63. })
  64. }
  65. _stopPlay () {
  66. if (this.useRAF) {
  67. cancelAnimationFrame(this.animTimer)
  68. this.animTimer = null
  69. } else {
  70. clearInterval(this.animTimer)
  71. this.animTimer = null
  72. }
  73. }
  74. stopAllFrameAnimation () {
  75. this.stopLoop = true
  76. this._stopPlay()
  77. }
  78. /**
  79. * 顺序播放
  80. * @param {Array} frameSections 动画帧片段
  81. */
  82. linearPlay (frameSections = this.frames) {
  83. return this._play(frameSections, this.currentIndex)
  84. }
  85. /**
  86. * 顺序循环播放
  87. * @param {Array} frameSections 动画帧片段
  88. */
  89. loopPlay (frameSections = this.frames) {
  90. this._play(frameSections, this.currentIndex).then((res) => {
  91. if (!this.stopLoop) {
  92. this.currentIndex = 0
  93. this.loopPlay(frameSections, this.currentIndex)
  94. }
  95. })
  96. }
  97. // 倒序播放
  98. reversePlay (frameSections = this.frames) {
  99. frameSections.reverse()
  100. return this.linearPlay(frameSections)
  101. }
  102. // 倒序循环播放
  103. reverseLoopPlay (frameSections = this.frames) {
  104. frameSections.reverse()
  105. this.loopPlay(frameSections)
  106. }
  107. // 秋千式(单摆式)循环播放:即从第一帧播放到最后一帧,再由最后一帧播放到第一帧,如此循环
  108. swingLoopPlay (frameSections = this.frames) {
  109. this._play(frameSections, this.currentIndex).then((res) => {
  110. if (!this.stopLoop) {
  111. this.currentIndex = 0
  112. frameSections.reverse()
  113. this.swingLoopPlay(frameSections)
  114. }
  115. })
  116. }
  117. /**
  118. * 销毁资源,需谨慎使用
  119. */
  120. disposeResource () {
  121. this.stopAllFrameAnimation()
  122. for (let i = 0; i < this.frames.length; i++) {
  123. this.frames[i] = null
  124. }
  125. this.frames = null
  126. this.canvas = this.ctx = null
  127. }
  128. }
  129. export default FrameAnim
CSS3 动画

css动画的原理是通过transition属性或@keyframes/animation定义元素在动画中的关键帧,以实现渐变式的过渡。

css动画有以下特点:

优点

CSS动画实现比较简单

CSS动画执行与JS主线程无关,例如在Chromium里,css动画运行在compositor thread线程中,即使你js线程卡住,css动画照常执行

强制使用硬件加速,能有效利用GPU

缺点

只能操作DOM或XML对象的部分属性

动画控制能力薄弱,不能逐帧定义动画状态

支持的缓动函数有限(CSS3动画的贝塞尔曲线是一个标准3次方曲线)

滥用硬件加速也会导致性能问题

最新活动
阅读需要支付1元查看
<