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

动画设计

知道动画处理的基本原理与算法了,那么 jQuery 在这个基础上封装扩展,让动画使用起来更灵活方便。

我归纳有几点:

基于 promise 的事件通知

得益于 deferred 的机制,可以让一个对象转化成带有 promise 的特性,实现了 done/fail/always/progress 等等一系列的事件反馈接口。

这样的设计我们并不陌生,在 ready、ajax 包括动画都是基于这样的异步模型的结构:

deferred = jQuery.Deferred()
//生成一个动画对象了
animation = deferred.promise({}) //混入动画的属性与方法

那么这样操作的一个好处就是,可以把逻辑处理都放到一块,我们在代码的某一个环节针对特别的处理,需要临时改变一些东西,但是在之后我们希望又恢复原样,为了逻辑的清晰,我们可以引入 deferred.alway 方法,在某一个环节改了一个属性,然后注册到 alway 方法上一个完成的回调用来恢复,这样的逻辑块是很清晰的。

增加属性的 show/hide/toggle 的快捷方式:

style.overflow = "hidden";
    anim.always(function() {
        //完成后恢复溢出
        style.overflow = opts.overflow[0];
        style.overflowX = opts.overflow[1];
        style.overflowY = opts.overflow[2];
    });

指定中文参数是比较特殊的,这种方式也是 jQuery 自己扩展的行为,逻辑上也很容易处理。

ook.animate({
      left: '50',
      height:'hide'
}

height 高度在动画结束之后隐藏元素,那么意味着元素本身的高度 height 也是需要改变的,从初始的位置慢慢的递减到 0 然后隐藏起来。

代码中有这么一段,针对 hide 的动作,我们在 done 之后会给元素直接隐藏起来。

//目标是显示
if (hidden) {
    jQuery(elem).show();
} else {
    //目标是隐藏
    anim.done(function() {
        jQuery(elem).hide();
    });
}

其实 show 与 hide 的流程是一样的,只是针对元素在初始与结束的一个状态的改变。

css 属性设置独立的缓动函数

在动画预初始化之后(为了支持动画,临时改变元素的一些属性与状态),我们就需要给每一个属性生成一个独立的缓动对象了 createTween,主要用于封装这个动画的算法与执行的一些流程操作控制。

//生成对应的缓动动画 //
createTween: function(prop, end) {
    var tween = jQuery.Tween(elem, animation.opts, prop, end,
        animation.opts.specialEasing[prop] || animation.opts.easing);
    //加入到缓动队列
    animation.tweens.push(tween);
    return tween;
}

tween 对象

通过这个结构大概就知道了,这个就是用于生成动画算法所需要的一些数据与算法的具体流程控制了。

属性预处理

我们知道元素本身在布局的时候可以用很多属性对其设置,可是一旦进行动画的话,某些属性的设置可能会对动画的执行产生副作用,所以针对这样的属性,jQuery 直接在内部做了最优的处理,如果我们进行元素 height/width 变化的时候,比如 height:1,这样的处理 jQuery 就需要针对元素做一些强制性的处理。

1 添加 overflow =“hidden”
2.如果设置了内联并且没有设置浮动 display = "inline-block";

因为内容溢出与内联元素在执行动画的时候,与这个 height/width 的逻辑是符合的,当然针对这样的修改 jQuery 非常巧妙了用到了 deferred.always 方法,我们在执行动画的时候,由于动画的需要改了原始的属性,但是动画在结束之后,我们还是需要还原成其状态。

deferred 量身定做的 always 方法,不管成功与失败都会执行这个复原的逻辑。

任务

  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>
下一节