乐趣区

jQuery源码解析之animate上

前言:
需要先看 jQuery 源码解析之 $.queue()、$.dequeue() 和 jQuery.Callbacks()

一、举例
divA 的宽度先变成 500px,再变成 300px,最后变成 1000px:

<script src="jQuery.js"></script>

  <div id="A" style="width:100px;height:50px;background-color: deeppink"> 这是 A </div>

<script>
  let A = document.querySelector('#A');
  // 在异步调用中,进行同步调用
  // 动画是异步的
  A.onclick = function() {// 就是连续调用 animation.add()
    $('#A').animate({'width': '500'}).animate({'width': '300'}).animate({'width': '1000'});
  };
</script>

二、$().animate()
作用:
通过 CSS 样式将元素从一个状态改变为另一个状态

源码:

  // 之前有说过:jQuery.fn.extend() 是 $()的方法
  jQuery.fn.extend( {
   // 源码 8062 行
    //{'width': '500'}
    animate: function(prop, speed, easing, callback) {
      // 是否是空对象,false
      var empty = jQuery.isEmptyObject(prop),
        // optall={//   complete:function(){jQuery.dequeue()},
        //   old:false,
        //   duration: 400,
        //   easing: undefined,
        //   queue:"fx",
        // }
        
        //undefined undefined undefined
        optall = jQuery.speed(speed, easing, callback),

        doAnimation = function() {
         
          // Operate on a copy of prop so per-property easing won't be lost
          //Animation 方法执行单个动画的封装
          //doAnimation 的本质是执行 Animation 方法

          //this: 目标元素
          //{'width': '500'}
          //optall={xxx}
          var anim = Animation(this, jQuery.extend( {}, prop ), optall );

          // Empty animations, or finishing resolves immediately
          //finish 是数据缓存的一个全局变量
          // 如果为 true,立即终止动画
          if (empty || dataPriv.get( this, "finish") ) {anim.stop( true);
          }
        };
      // 注意这个
      // 自身的.finish= 自身
      doAnimation.finish = doAnimation;

      return empty || optall.queue === false ?
        this.each(doAnimation) :
        // 一般走这里
        // 通过 queue 调度动画之间的衔接
        //optall.queue:"fx"
        //doAnimation:function(){}
        this.queue(optall.queue, doAnimation);
    },

  })

解析:
(1)jQuery.speed()
作用:
初始化动画对象的属性

源码:

  // 源码 8009 行
  //undefiend undefined undefined

  // 作用是返回一个经过修改的 opt 对象
  jQuery.speed = function(speed, easing, fn) {
    // opt={
    //   complete:false,
    //   duration: undefined,
    //   easing: undefined,
    // }

    var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed ) : {
      complete: fn || !fn && easing ||
        isFunction(speed) && speed,
      duration: speed,
      easing: fn && easing || easing && !isFunction(easing) && easing
    };
    
    // Go to the end state if fx are off
    /* 这边是为 opt.duration 赋值的 */

    //undefiend
    if (jQuery.fx.off) {opt.duration = 0;} else {if ( typeof opt.duration !== "number") {if ( opt.duration in jQuery.fx.speeds) {opt.duration = jQuery.fx.speeds[ opt.duration];

        } else {
          // 走这边
          //_default=400
          opt.duration = jQuery.fx.speeds._default;
        }
      }
    }
    /* 为 opt.queue 赋值 */
    // Normalize opt.queue - true/undefined/null -> "fx"
    // 注意这个 ==,是模糊判断
    if (opt.queue == null || opt.queue === true) {opt.queue = "fx";}

    // Queueing
    /* 为 opt.old 赋值 */
    opt.old = opt.complete;

    opt.complete = function() {if ( isFunction( opt.old) ) {opt.old.call( this);
      }
      // 如果 queue 有值得话,就出队列
      // 因为 queue 默认为 "fx",所以一般会触发 dequeue 操作
      if (opt.queue) {
        //this 指目标元素
        //opt.queue
        jQuery.dequeue(this, opt.queue);
      }
    };
    // 此时的 opt 为:// opt={//   complete:function(){jQuery.dequeue()},
    //   old:false,
    //   duration: 400,
    //   easing: undefined,
    //   queue:"fx",
    // }
    return opt;
  };

解析:
通过传入的三个参数,对 opt 对象进行处理,如果三个参数都为 undefined 的话,opt等于:

   opt={complete:function(){jQuery.dequeue()},
       old:false,
       duration: 400,
       easing: undefined,
       queue:"fx",
   }

(2)animate内部做了什么?
作用:
animate内部封装了一个 doAnimation 触发器,触发器触发就会运行 Animation 方法,animate最后返回的是 queue() 方法,注意 queue() 方法的参数带有触发器doAnimation

(3)$().queue()
作用:
执行入队操作(jQuery.queue()),如果是 fx 动画的话,同时执行出队操作(jQuery.dequeue()

源码
这个方法上篇文章已经分析过了,这里就简单分析下:

  jQuery.fn.extend( {
    //optall.queue:"fx"
    //doAnimation:function(){}

    //fx 动画的话,就执行 dequeue(),也就是队首 callback 函数
    queue: function(type, data) {// 返回的是数组[doAnimate,doAnimate,xxx]
          var queue = jQuery.queue(this, type, data);
 
          // 初始化 hooks
          jQuery._queueHooks(this, type);
          /* 专门为 fx 动画做处理 */
          // 如果队首没有锁的话,直接运行队首元素
          if (type === "fx" && queue[ 0] !== "inprogress" ) {jQuery.dequeue( this, type);
          }

    },

解析:
inprogress是动画队列的锁,目的是 保证上个动画执行结束后,再去执行下个动画

每入队一个 doAnimate 函数,如果队首没有 inprogress 锁的话,就会出队去运行一个 doAnimate 函数

jQuery._queueHooks()的意义在于添加一个 empty.remove() 方法,用来清空队列queue

(4)jQuery.queue()
作用:
上篇文章也分析过了,就是将 doAnimate 函数 pushqueue数组中

(5)jQuery.dequeue()
作用:
如果队首元素不是 inprogress,而是doAnimation 方法,则先将 doAnimation 出队,再让 inprogress 入队首,再运行 doAnimation 方法;
如果队首元素是 inprogress 的话,则移除锁

如果队列为空的话,则清空queue,节省内存

源码:

   // 源码 4624 行
    // 目标元素,'type'
    dequeue: function(elem, type) {
      //'type'
      type = type || "fx";
      //get,获取目标元素的队列
      var queue = jQuery.queue(elem, type),
        // 长度
        startLength = queue.length,
        // 去除对首元素,并返回该元素
        fn = queue.shift(),
        // 确保该队列有一个 hooks
        hooks = jQuery._queueHooks(elem, type),
        //next 相当于 dequeue 的触发器
        next = function() {jQuery.dequeue( elem, type);
        };

      // If the fx queue is dequeued, always remove the progress sentinel
      // 如果 fn='inprogress',说明是 fx 动画队列正在出队,就移除 inprogress
      if (fn === "inprogress") {fn = queue.shift();
        startLength--;
      }

      if (fn) {

        // Add a progress sentinel to prevent the fx queue from being
        // automatically dequeued
        // 如果是 fx 动画队列的话,就添加 inprogress 标志,来防止自动出队执行
        // 意思应该是等上一个动画执行完毕后,再执行下一个动画
        if (type === "fx") {queue.unshift( "inprogress");
        }

        // Clear up the last queue stop function
        // 删除 hooks 的 stop 属性方法
        delete hooks.stop;
        // 递归 dequeue 方法
        fn.call(elem, next, hooks);
      }
      console.log(startLength,'startLength4669')
      // 如果队列是一个空数组,并且 hooks 存在的话,清除该队列
      if (!startLength && hooks) {
        // 进行队列清理
        hooks.empty.fire();
        console.log(hooks.empty.fire(),'bbbb4671')
      }
    },

解析:
循环同步运行多个 doAnimation() 方法,直到队列为空

综上,除 doAnimation 内的逻辑外,整个 $().animate() 的流程图 为:

退出移动版