资讯专栏INFORMATION COLUMN

jQuery源码解析之$().animate()(下)

raledong / 2052人阅读

摘要:根据的间隔,利用循环执行,从而达到渲染动画的目的。最后,附上的流程图,建议配合整个的流程图二的最后一个图一起看下篇将会模拟实现方法,敬请期待完

三、doAnimation内部的Animation()方法
作用:
$().animate()核心方法

源码:

  //animate()核心方法
  //源码7844行
  //elem:目标元素

  //this:目标元素
  //{"width": "500"}
  // optall={
  //   complete:function(){jQuery.dequeue()},
  //   old:false,
  //   duration: 400,
  //   easing: undefined,
  //   queue:"fx",
  // }

  function Animation( elem, properties, options ) {
    var result,
      stopped,
      index = 0,
      //1
      length = Animation.prefilters.length,
      //{
      // always:function(){},
      // catch:function(){},
      // done:function(){},
      // xxx
      // }

      //初始化deferred对象
      //deferred.always()表示不管成功还是失败,最终都会运行内部设置的代码
      deferred = jQuery.Deferred().always( function() {

        // Don"t match elem in the :animated selector
        delete tick.elem;
      } ),
      
      tick = function() {
        if ( stopped ) {
          return false;
        }
        var currentTime = fxNow || createFxNow(),
          remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),

          // Support: Android 2.3 only
          // Archaic crash bug won"t allow us to use `1 - ( 0.5 || 0 )` (#12497)
          temp = remaining / animation.duration || 0,
          percent = 1 - temp,
          index = 0,
          length = animation.tweens.length;

        for ( ; index < length; index++ ) {
          animation.tweens[ index ].run( percent );
        }

        deferred.notifyWith( elem, [ animation, percent, remaining ] );

        // If there"s more to do, yield
        if ( percent < 1 && length ) {
          return remaining;
        }

        // If this was an empty animation, synthesize a final progress notification
        if ( !length ) {
          deferred.notifyWith( elem, [ animation, 1, 0 ] );
        }

        // Resolve the animation and report its conclusion
        deferred.resolveWith( elem, [ animation ] );
        return false;
      },
      //==========tick end==========
      //让animation带有promise的属性,并在其中添加动画的属性和方法
      animation = deferred.promise( {
        elem: elem,
        props: jQuery.extend( {}, properties ),
        opts: jQuery.extend( true, {
          specialEasing: {},
          easing: jQuery.easing._default
        }, options ),
        originalProperties: properties,
        originalOptions: options,
        startTime: fxNow || createFxNow(),
        duration: options.duration,
        tweens: [],
        //500,"width",animation
        createTween: function( prop, end ) {
          var tween = jQuery.Tween( elem, animation.opts, prop, end,
            animation.opts.specialEasing[ prop ] || animation.opts.easing );
          animation.tweens.push( tween );
          // {
          //   easing: "swing"
          //   elem: div#A
          //   end: 500
          //   now: 500
          //   options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
          //   pos: 1
          //   prop: "width"
          //   start: 100
          //   unit: "px"
          // }
          return tween;
        },
        stop: function( gotoEnd ) {
          var index = 0,

            // If we are going to the end, we want to run all the tweens
            // otherwise we skip this part
            length = gotoEnd ? animation.tweens.length : 0;
          if ( stopped ) {
            return this;
          }
          stopped = true;
          for ( ; index < length; index++ ) {
            animation.tweens[ index ].run( 1 );
          }

          // Resolve when we played the last frame; otherwise, reject
          if ( gotoEnd ) {
            deferred.notifyWith( elem, [ animation, 1, 0 ] );
            deferred.resolveWith( elem, [ animation, gotoEnd ] );
          } else {
            deferred.rejectWith( elem, [ animation, gotoEnd ] );
          }
          return this;
        }
      } ),
      //===========animation end===============
      props = animation.props;
    //{width:500},undefined
    propFilter( props, animation.opts.specialEasing );

    for ( ; index < length; index++ ) {
      result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
      if ( result ) {
        if ( isFunction( result.stop ) ) {
          jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
            result.stop.bind( result );
        }
        return result;
      }
    }
    /*运行动画*/
    // createTween(500,"width",animation)
    jQuery.map( props, createTween, animation );

    if ( isFunction( animation.opts.start ) ) {
      animation.opts.start.call( elem, animation );
    }

    // Attach callbacks from options
    animation
      .progress( animation.opts.progress )
      .done( animation.opts.done, animation.opts.complete )
      .fail( animation.opts.fail )
      .always( animation.opts.always );

    jQuery.fx.timer(
      //让tick方法继承elem、anim和queue属性
      jQuery.extend( tick, {
        elem: elem,
        anim: animation,
        queue: animation.opts.queue
      } )
    );

    return animation;
  }

解析:

(1)Animation.prefilters
源码:

jQuery.Animation = jQuery.extend( Animation, {
    //源码8175行
    //defaultPrefilter是一个function
    prefilters: [ defaultPrefilter ],
})

所以Animation.prefilters=1defaultPrefilter的源码暂不解析

(2)关于jQuery.Deferred()的解释,请参考:jQuery中的Deferred详解和使用

(3)jQuery.map(elems, callback, arg)
作用:
根据elems数量,循环运行callback( elems[ i ], i, arg )

源码:

jQuery.extend( {
    // arg is for internal usage only
    //源码524行
    //props, createTween, animation
    map: function( elems, callback, arg ) {
      var length, value,
        i = 0,
        ret = [];

      // Go through the array, translating each of the items to their new values
      //如果elems是类数组的话
      if ( isArrayLike( elems ) ) {
        length = elems.length;
        for ( ; i < length; i++ ) {
          value = callback( elems[ i ], i, arg );

          if ( value != null ) {
            ret.push( value );
          }
        }

        // Go through every key on the object,
      } else {
        //走这边
        for ( i in elems ) {
          //500 width animation
          /*执行动画*/
          value = callback( elems[ i ], i, arg );

          if ( value != null ) {
            ret.push( value );
          }
        }
      }
      console.log(ret,"ret555")
      // Flatten any nested arrays
      // 展平任何嵌套数组
      return concat.apply( [], ret );
    },

})

解析:
根据例子的话,就是:

createTween(500,"width",animation)
createTween(300,"width",animation)
createTween(1000,"width",animation)

(4)jQuery内部函数createTween(value, prop, animation)
作用:
animation调用Animation.tweeners[ "*" ]中的方法

源码:

  //源码7752行
  //创建动画对象
  // createTween(500,"width",animation)
  function createTween( value, prop, animation ) {
    var tween,
      //[ function( prop, value ) {
      //         var tween = this.createTween( prop, value );
      //         console.log("vvvv","aaa8083")
      //         adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
      //         return tween;
      //       } ]
      collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
      index = 0,
      //1
      length = collection.length;
    for ( ; index < length; index++ ) {
      //prop:width
      //value:500
      //运行collection[ index ],this绑定animation
      if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {

        // We"re done with this property
        return tween;
      }
    }
  }

(5)Animation.tweeners[ "*" ]
作用:
animation调用Animation.tweeners[ "*" ]中的方法

  jQuery.Animation = jQuery.extend( Animation, {
    //源码8152行
    tweeners: {
      //prop:width
      //value:500
      "*": [ function( prop, value ) {
        //animation.createTween
        var tween = this.createTween( prop, value );
        adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
        return tween;
      } ]
    },

})

解析:
返回经过animation. createTween("width",500)处理和adjustCSS()处理的变量tween

① animation. createTween("width",500)
animationAnimation()方法中封装的一个对象(对象keyvaluefunction

作用:
根据开发者传入的属性,将其转化为一个对象,对象内部的属性时执行动画所需要的属性。

源码:

animation = deferred.promise( {
   //500,"width",animation
   createTween: function( prop, end ) {
      var tween = jQuery.Tween( elem, animation.opts, prop, end,
          animation.opts.specialEasing[ prop ] || animation.opts.easing );
          animation.tweens.push( tween );
          // {
          //   easing: "swing"
          //   elem: div#A
          //   end: 500
          //   now: 500
          //   options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
          //   pos: 1
          //   prop: "width"
          //   start: 100
          //   unit: "px"
          // }
          return tween;
      },
})

解析:
调用jQuery.Tween获得tween对象,并把tween对象放进animation.tweens数组中

② 简单看下jQuery.Tween源码:

  //源码7568行
  function Tween( elem, options, prop, end, easing ) {
    //width 500 swing
    //width 300 swing
    //width 1000 swing
    return new Tween.prototype.init( elem, options, prop, end, easing );
  }
  jQuery.Tween = Tween;

  Tween.prototype = {
    constructor: Tween,
    init: function( elem, options, prop, end, easing, unit ) {
      this.elem = elem;
      this.prop = prop;
      this.easing = easing || jQuery.easing._default;
      this.options = options;
      this.start = this.now = this.cur();
      this.end = end;
      this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
    },
    cur: function() {},
    run: function( percent ) {},
  };

  Tween.prototype.init.prototype = Tween.prototype;

执行jQuery.Tween方法,就是new一个对象,就是执行jQuery.Tween.init()方法,根据{width:500}生成的动画对象如下:

{
  easing: "swing"
  elem: div#A
  end: 500
  now: 500
  options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
   pos: 1
   prop: "width"
   start: 100
   unit: "px"
}

③ 关于adjustCSS的解析,请看:jQuery源码解析(4)—— css样式、定位属性

Animation.tweeners[ "*" ]方法最终返回的tween如下:

{
  easing: "swing"
  elem: div#A
  end: 500
  now: 500
  options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
   pos: 1
   prop: "width"
   start: 100
   unit: "px"
}

综上,jQuery.map()最终作用就是将$().animate()中的参数转化为动画对象,并pushanimation.tweens数组中

(6)jQuery.fx.timer()
作用:
依次执行timer

源码:

  //源码8504行
  //单个动画内部执行
  jQuery.fx.timer = function( timer ) {
    //将Animation.tick()依次放进jQuery.timers数组中
    jQuery.timers.push( timer );
    //每push进一个,就运行一个
    jQuery.fx.start();
  };

jQuery.timers是一个数组:

//源码8431行
  jQuery.timers = [];

(7)jQuery.fx.start()
作用:
在动画运行前,加锁,并运行动画

源码:

  //源码8514行
  //加锁,运行动画
  jQuery.fx.start = function() {
    if ( inProgress ) {
      return;
    }
    //动画开始即为运行中,加上锁
    inProgress = true;
    //运行
    schedule();
  };

注意:inProgress 锁是控制整个动画流程的锁,而不是单个动画队列的锁

(8)schedule()
作用:
如果动画已经开始(inProgress=true),那么就不断执行jQuery.fx.tick()方法(动画渲染)

源码:

  //源码7694行
  //如果动画已经开始,那么就不断执行jQuery.fx.tick()方法(动画渲染)
  function schedule() {
    //inProgress是判断整个动画流程是否结束的标志
    //当inProgress=null时,整个动画结束
    if ( inProgress ) {
      //走这边
      if ( document.hidden === false && window.requestAnimationFrame ) {
        //使用requestAnimationFrame来完成动画
        //递归
        window.requestAnimationFrame( schedule );
      } else {
        //13代表动画每秒运行的帧数,可以保证浏览器能完成动画
       //jQuery.fx.interval = 13;
        window.setTimeout( schedule, jQuery.fx.interval );
      }
      /*执行动画*/
      jQuery.fx.tick();
    }
  }

(9)jQuery.fx.tick()
作用:
运行Animation.tick()并安全地移除它

源码:

  //源码8483行
  //运行Animation.tick()并安全地移除它
  jQuery.fx.tick = function() {
    var timer,
      i = 0,
      timers = jQuery.timers;

    fxNow = Date.now();
    //这里的timers,就是Animation.tick()的集合
    for ( ; i < timers.length; i++ ) {
      timer = timers[ i ];

      // Run the timer and safely remove it when done (allowing for external removal)
      //运行Animation.tick()并安全地移除它
      if ( !timer() && timers[ i ] === timer ) {
        timers.splice( i--, 1 );
      }
    }
    //inProgress=null,停止动画
    if ( !timers.length ) {
      jQuery.fx.stop();
    }
    fxNow = undefined;
  };

  //源码8474行
  //结束整个动画流程
  jQuery.fx.stop = function() {
    inProgress = null;
  };

(10)Animation.tick()
作用:
根据动画的参数来执行动画

源码:

function Animation( elem, properties, options ) {
//根据动画的参数来执行动画
      tick = function() {
        if ( stopped ) {
          return false;
        }
        //当前时间的时间戳
        var currentTime = fxNow || createFxNow(),
          //动画时长默认400ms
          //开始时间+动画时长-当前时间
          //在每次调用requestAnimationFrame后,记录下剩下的的时间在总时间(duration)中的位置
          remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),

          // Support: Android 2.3 only
          // Archaic crash bug won"t allow us to use `1 - ( 0.5 || 0 )` (#12497)
          //剩下的时间占总时长的占比
          temp = remaining / animation.duration || 0,
          //当前时间占总时长的占比
          percent = 1 - temp,
          index = 0,
          length = animation.tweens.length;

        for ( ; index < length; index++ ) {
          //根据传入的动画参数和当前进程的百分比来运行动画
          animation.tweens[ index ].run( percent );
        }

        deferred.notifyWith( elem, [ animation, percent, remaining ] );

        // If there"s more to do, yield
        if ( percent < 1 && length ) {
          return remaining;
        }

        // If this was an empty animation, synthesize a final progress notification
        if ( !length ) {
          deferred.notifyWith( elem, [ animation, 1, 0 ] );
        }

        // Resolve the animation and report its conclusion
        deferred.resolveWith( elem, [ animation ] );
        return false;
      },

}

解析:
通过动画持续时间duration、动画开始时间animation.startTime和每次调用requestAnimationFrame后动画结束时间currentTime,计算出此帧在整个动画流程中的占比,从而较为准确绘制动画

(11)Tween.run()
作用:
绘制动画帧

源码:

  Tween.prototype = {
      run: function( percent ) {
      // {
      //   easing: "swing"
      //   elem: div#A
      //   end: 500
      //   now: 105.52601592046467
      //   options: {specialEasing: {…}, easing: "swing", complete: ƒ, duration: 400, queue: "fx", …}
      //   pos: 1
      //   prop: "width"
      //   start: 100
      //   unit: "px"
      // }
      var eased,
        //undefiend
        hooks = Tween.propHooks[ this.prop ];
      //400
      if ( this.options.duration ) {
        //swing,两边慢中间快
        //动画效果
        this.pos = eased = jQuery.easing[ this.easing ](
          percent, this.options.duration * percent, 0, 1, this.options.duration
        );
      } else {
        this.pos = eased = percent;
      }
      //width的宽度
      this.now = ( this.end - this.start ) * eased + this.start;

      if ( this.options.step ) {
        this.options.step.call( this.elem, this.now, this );
      }

      if ( hooks && hooks.set ) {
        hooks.set( this );
      } else {
        //走这边
        //执行style变化
        Tween.propHooks._default.set( this );
      }
      return this;
    },

  }

解析:
一个是动画效果swing的处理:jQuery.easing[ this.easing ](percent, this.options.duration * percent, 0, 1, this.options.duration);

另一个就是关键style变化了:Tween.propHooks._default.set( this )

(12)Tween.propHooks._default.set()
作用:
执行style变化

源码:

  Tween.propHooks = {
    _default: {
      //源码7661行
      set: function( tween ) {  
        // Use step hook for back compat.
        // Use cssHook if its there.
        // Use .style if available and use plain properties where available.
        //undefined
        if ( jQuery.fx.step[ tween.prop ] ) {
          jQuery.fx.step[ tween.prop ]( tween );
        } else if ( tween.elem.nodeType === 1 &&
          ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
            jQuery.cssHooks[ tween.prop ] ) ) {
          //走这边
          //#A,width,100px(103px,134px,xxx)
          jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
        } else {
          tween.elem[ tween.prop ] = tween.now;
        }
      },

  }
}

解析:
tween.now,是每次requestAnimationFrame要变化的width的值,tween.unitpx,所以这段代码最终执行的是jQuery.style( 目标元素, 要变化的style属性, 要变化的值 )

(13)jQuery.style()
作用:
设置 DOM 节点的 style 属性

简略的源码:

    // Get and set the style property on a DOM Node
    //源码7279行
    style: function( elem, name, value, extra ) {
         elem.style[ name ] = value
    }

综上,Animation() 有两大作用:
(1)将传入的动画对象处理成jQuery的动画对象。
(2)根据duration的间隔,利用requestAnimationFrame循环执行style,从而达到渲染动画的目的。

最后,附上 doAnimation() 的流程图,建议配合整个$().animate()的流程图(二、的最后一个图)一起看:

下篇将会模拟实现$().animate() 方法,敬请期待!

(完)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105056.html

相关文章

  • jQuery源码解析$().animate()(上)

    摘要:前言需要先看源码解析之和一举例的宽度先变成,再变成,最后变成这是在异步调用中,进行同步调用动画是异步的就是连续调用二作用通过样式将元素从一个状态改变为另一个状态源码之前有说过是的方法源码行是否是空对象,方法执行单个动画的封装的本质是执行 showImg(https://segmentfault.com/img/remote/1460000019594521); 前言:需要先看 jQue...

    Batkid 评论0 收藏0
  • jQuery模拟实现$().animate()(

    摘要:前言在上篇的基础上,接入逻辑图实现之的实现这是匿名函数自调用,下面好长好长的就是也就是说是一个这里也是匿名函数自调用本质就是经过一系列操作得到并作为参数,赋值给匹配初始化模仿动画效果两头慢,中间快创建动画缓动对象动画缓动算法 showImg(https://segmentfault.com/img/remote/1460000019626160); 前言:在上篇的基础上,接入doAni...

    Coding01 评论0 收藏0

发表评论

0条评论

raledong

|高级讲师

TA的文章

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