7-8 动画实现
本节编程练习不计算学习进度,请电脑登录imooc.com操作

动画实现

在动画算法与设计的小节中我都把动画一些相关的东西单独提出来分析了,其实 jQuery 的动画的原理就是这样的,这一节我们把设计与算法融合在一起来实现一个伪 jQuery 的动画模块。

在 jQuery 的设计中 ready、ajax、动画都是支持 Promise 这个概念的,也就是通过 Deferred 提供了一个抽象的非阻塞的解决方案,它创建一个 promise 对象,其目的是在未来某个时间点返回一个响应,对应的也就是 done、fail、progress、complete、always等方法,所以在第一部分理解 Deferred 对象是非常重要的,这样才能读懂这类设计。

具体的代码我们可以如右图所示,这里我们开始解释下其设计的目的。

book.animate({
    left: '500'
}, {
    duration: 2000
})

就这个动画而言,我们要涉及几个问题,left:500 其实是最终的坐标值。

那么完成这个 left 动画我们至少需要:

每一种属性其实都是有各自的变换的一个算法,包括计算开始值、变化值、时间等等,所以我们必须给每一个变换的属性都进行一个包装,用来封装各自的相关数据,那么我们其实可以用一个类来构造跟属性变化相关的一些信息,如 Tween 类。

通过一个 Tween 类构造出来的缓动对象,其实就是针对每一个属性的封装对象,这样我们只需要设计一个定时器,在指定的时间内调用 Tween 生成的这些对象就可以了,Tween 内部控制着各自属性的状态改变。


具体右边的实现代码涉及了如下几个部分了:

  1. Animation 函数,入口函数用来做一些参数的初始化工作,整个动画的开始调度
  2. animation 对象就是实际的动画对象了,通过 Animation 函数创建,这个对象上实现了所有属性与方法
  3. new Tween() 通过 Tween 创建每一个属性对象相关的数据
  4. animation.tweens 保存了每一个属性对象的容器
  5. Animation.fx 就是具体开始动画执行的调用的一个调度对象了
  6. 定时器都是执行一个回调函数的,tick 就是定时器执行的回调,在 tick 函数中通过计算出变化数据,然后通过循环 animation.tweens 中的每一个动画属性对象,来实现改变

其实动画的整个设计就是这么简单的,代码的复杂是因为还兼容了各种不同情况的处理,比如实现 deferred 机制,针对宽高变化的 overflow 处理,针对 display 为 inline 情况下的处理,那么这些都是一些预处理的机制,在对应的 defaultPrefilter 函数中就能找到,我们没有必要是单独实现了。

其实动画的设计,我们可以学到一个很重要的一点: 封装变化,把每一个属性变化都独立封装一个对象,让其自己管理与控制。

任务

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
  5. <style type="text/css">
  6. button{
  7. display: block;
  8. }
  9. </style>
  10. <script src="http://code.jquery.com/jquery-latest.js"></script>
  11. </head>
  12. <body>
  13.  
  14. <button id="one">jQuery动画的模拟实现:left:50</button>
  15. <button id="two">jQuery动画的模拟实现:left:500</button>
  16.  
  17. <ul id="book" alt="" style="background:red;opacity:1;position: relative; left: 300px;width:200px;height:100px;display:inline;"></ul>
  18.  
  19.  
  20. <script type="text/javascript">
  21.  
  22.  
  23. var book = document.getElementById('book');
  24. var $book = $('#book');
  25. var i = 10
  26. while(i){
  27. $book.append("<li>11</li>")
  28. i--;
  29. }
  30.  
  31.  
  32. ////////////
  33. //创建动画缓动对象 //
  34. ////////////
  35. function Tween(value, prop, animation) {
  36. this.elem = animation.elem;
  37. this.prop = prop;
  38. this.easing = "swing"; //动画缓动算法
  39. this.options = animation.options;
  40. //获取初始值
  41. this.start = this.now = this.get();
  42. //动画最终值
  43. this.end = value;
  44. //单位
  45. this.unit = "px"
  46. }
  47.  
  48. function getStyles(elem) {
  49. return elem.ownerDocument.defaultView.getComputedStyle(elem, null);
  50. };
  51.  
  52. function swing(p) {
  53. return 0.5 - Math.cos(p * Math.PI) / 2;
  54. }
  55.  
  56. Tween.prototype = {
  57. //获取元素的当前属性
  58. get: function() {
  59. var computed = getStyles(this.elem);
  60. var ret = computed.getPropertyValue(this.prop) || computed[this.prop];
  61. return parseFloat(ret);
  62. },
  63. //运行动画
  64. run:function(percent){
  65. var eased
  66. //根据缓动算法改变percent
  67. this.pos = eased = swing(percent);
  68. //获取具体的改变坐标值
  69. this.now = (this.end - this.start) * eased + this.start;
  70. //最终改变坐标
  71. this.elem.style[this.prop] = this.now + "px";
  72. return this;
  73. }
  74. }
  75.  
  76.  
  77. ////////
  78. //动画类 //
  79. ////////
  80. function Animation(elem, properties, options){
  81. //动画对象
  82. var animation = {
  83. elem : elem,
  84. props : properties,
  85. originalOptions : options,
  86. options : options,
  87. startTime : Animation.fxNow || createFxNow(),//动画开始时间
  88. tweens : [] //存放每个属性的缓动对象,用于动画
  89. }
  90.  
  91. //生成属性对应的动画算法对象
  92. for (var k in properties) {
  93. // tweens保存每一个属性对应的缓动控制对象
  94. animation.tweens.push( new Tween(properties[k], k, animation) )
  95. }
  96.  
  97. //动画状态
  98. var stopped;
  99. //动画的定时器调用包装器
  100. var tick = function() {
  101. if (stopped) {
  102. return false;
  103. }
  104. //动画时间算法
  105. var currentTime = Animation.fxNow || createFxNow
  106. //运动时间递减
  107. remaining = Math.max(0, animation.startTime + animation.options.duration - currentTime),
  108. temp = remaining / animation.options.duration || 0,
  109. percent = 1 - temp;
  110.  
  111. var index = 0,
  112. length = animation.tweens.length;
  113.  
  114. //执行动画改变
  115. for (; index < length; index++) {
  116. //percent改变值
  117. animation.tweens[index].run(percent);
  118. }
  119.  
  120. //是否继续,还是停止
  121. if (percent <= 1 && length) {
  122. return remaining;
  123. } else {
  124. //停止
  125. return false;
  126. }
  127.  
  128. }
  129. tick.elem = elem;
  130. tick.anim = animation
  131.  
  132. Animation.fx.timer(tick)
  133. }
  134.  
  135. //创建开始时间
  136. function createFxNow() {
  137. setTimeout(function() {
  138. Animation.fxNow = undefined;
  139. });
  140. return (Animation.fxNow = Date.now());
  141. }
  142.  
  143.  
  144. //用于定时器调用
  145. Animation.timers =[]
  146.  
  147. Animation.fx = {
  148. //开始动画队列
  149. timer: function(timer) {
  150. Animation.timers.push(timer);
  151. if (timer()) {
  152. //开始执行动画
  153. Animation.fx.start();
  154. } else {
  155. Animation.timers.pop();
  156. }
  157. },
  158. //开始循环
  159. start: function() {
  160. if (!Animation.timerId) {
  161. Animation.timerId = setInterval(Animation.fx.tick, 13);
  162. }
  163. },
  164. //停止循环
  165. stop:function(){
  166. clearInterval(Animation.timerId);
  167. Animation.timerId = null;
  168. },
  169. //循环的的检测
  170. tick: function() {
  171. var timer,
  172. i = 0,
  173. timers = Animation.timers;
  174.  
  175. Animation.fxNow = Date.now();
  176.  
  177. for (; i < timers.length; i++) {
  178. timer = timers[i];
  179. if (!timer() && timers[i] === timer) {
  180. //如果完成了就删除这个动画
  181. timers.splice(i--, 1);
  182. }
  183. }
  184.  
  185. if (!timers.length) {
  186. Animation.fx.stop();
  187. }
  188. Animation.fxNow = undefined;
  189. }
  190. }
  191.  
  192. $("#one").click(function(){
  193. Animation(book, {
  194. left: '50'
  195. }, {
  196. duration: 2000
  197. })
  198. });
  199.  
  200. $("#two").click(function() {
  201. Animation(book, {
  202. left: '500'
  203. }, {
  204. duration: 2000
  205. })
  206. });
  207.  
  208.  
  209. </script>
  210.  
  211. </body>
  212. </html>
返回课程