美文网首页
jQuery源码解析之$().animate()(下)

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

作者: 小进进不将就 | 来源:发表于2019-06-27 09:22 被阅读0次

    三、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() 方法,敬请期待!


    (完)

    相关文章

      网友评论

          本文标题:jQuery源码解析之$().animate()(下)

          本文链接:https://www.haomeiwen.com/subject/wfphcctx.html