前言:
在上篇的基础上,接入doAnimation()

逻辑图:

实现:

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>jQuery之$().animate()的实现</title></head><body><!--<script src="jQuery.js"></script>--><div id="A" style="width:100px;height:50px;background-color: deeppink">这是A</div><script>  // (function(a){  //   console.log(a) //name  // })('name')  //  // (function (b) {  //   console.log(b) //function(){console.log('name')}  // })(function () {  //   console.log('name')  // })  //匿名函数自调用,下面好长好长的function就是$  //也就是说$是一个function(){xxx}  (function($) {    window.$ = $;  })(    //这里也是匿名函数自调用    //本质就是经过一系列操作得到chenQuery并作为参数$,赋值给window.$    function() {      //匹配ID      let rquickExpr = /^(?:#([\w-]*))$/;      //jQuery初始化      function chenQuery(selector) {        return new chenQuery.fn.init(selector);      }      function getStyles(elem) {        return elem.ownerDocument.defaultView.getComputedStyle(elem, null);      }      //模仿swing动画效果      //两头慢,中间快      function swing(p) {        return 0.5 - Math.cos(p * Math.PI) / 2;      }      //创建动画缓动对象//      function Tween(value, prop, animation) {        this.elem=animation.elem;        this.prop=prop;        this.easing= "swing"; //动画缓动算法        this.options=animation.options;        //获取初始值        this.start=this.now = this.get();        //动画最终值        this.end= value;        //单位        this.unit="px"      }      Tween.prototype = {        //获取元素的当前属性        get: function() {          let computed = getStyles(this.elem);          let ret = computed.getPropertyValue(this.prop) || computed[this.prop];          return parseFloat(ret);        },        //运行动画        run:function(percent){          let eased          //根据缓动算法改变percent          this.pos = eased = swing(percent);          //获取具体的改变坐标值          this.now = (this.end - this.start) * eased + this.start;          //最终改变坐标          this.elem.style[this.prop] = this.now + "px";          return this;        }      }      //创建开始时间      function createFxNow() {        setTimeout(function() {          Animation.fxNow = undefined;        });        return (Animation.fxNow = Date.now());      }      let inProgress      function schedule() {        //inProgress是判断整个动画流程是否结束的标志        //当inProgress=null时,整个动画结束        if ( inProgress ) {          //走这边          //使用requestAnimationFrame来完成动画          //递归          window.requestAnimationFrame( schedule );          /*执行动画帧*/          Animation.fx.tick();        }      }      // 动画核心函数      function Animation(elem, options, optall,func,){        //动画对象        let animation = {          elem:elem,          props:options,          originalOptions:optall,          options:optall,          //动画开始时间          startTime:Animation.fxNow || createFxNow(),          //存放每个属性的缓动对象,用于动画          tweens:[]        }        //生成属性对应的动画算法对象        for (let k in options) {          // tweens保存每一个属性对应的缓动控制对象          animation.tweens.push( new Tween(options[k], k, animation) )        }        //动画状态        let stopped;        //动画的定时器调用包装器        //单帧循环执行        let tick = function() {          if (stopped) {            return false;          }          //动画时间算法          let currentTime = Animation.fxNow || createFxNow,            //动画运动时间递减            remaining = Math.max(0, animation.startTime + animation.options.duration - currentTime),            //百分比            temp = remaining / animation.options.duration || 0,            percent = 1 - temp;          let index = 0,            length = animation.tweens.length;          //执行动画改变          for (; index < length; index++) {            //percent改变值            animation.tweens[index].run(percent);          }          //当进度不到100%时,继续绘制动画帧          if (percent < 1 && length) {            return remaining;          }          //当结束时通知单个动画结束          tick.complete()          return false        }        tick.elem = elem;        tick.anim = animation        //这个是自定义的属性,也就是单个动画结束的标志        tick.complete = func        //开始执行下个动画        Animation.fx.timer(tick)      }      //用于requestAnimationFrame调用      Animation.timers =[]      Animation.fx = {        //开始动画队列,不是帧队列        //Animation.tick()        timer: function(timer,) {          Animation.timers.push(timer);          if (timer()) {            //开始执行动画            Animation.fx.start();            // func()          }          // else {          //   Animation.timers.pop();          // }        },        //开始循环        start: function(func) {          if ( inProgress ) {            return;          }          //动画开始即为运行中,加上锁          inProgress = true;          //运行          schedule();          // func()        },        //停止循环        stop:function(){          inProgress = null;        },        //动画帧循环的的检测        tick: function() {          var timer,            i = 0,            timers = Animation.timers;          Animation.fxNow = Date.now();          for (; i < timers.length; i++) {            timer = timers[i];            if (!timer() && timers[i] === timer) {              //如果完成了就删除这个动画              timers.splice(i--, 1);            }          }          if (!timers.length) {            Animation.fx.stop();          }          Animation.fxNow = undefined;        }      }      //假设是在数据缓存中存取队列      const Queue=[]      //数据缓存      const dataPriv={        get:function (type) {          if(type==='queue') return Queue        },      }      const dequeue=function() {        const Queue=dataPriv.get("queue")        let fn = Queue.shift()        //当单个动画结束后,执行下个动画        const next = function() {          dequeue();        }        if ( fn === "inprogress" ) {          fn = Queue.shift();        }        if (fn) {          Queue.unshift( "inprogress" );          /*执行doAnimation方法,doAnimation(element, options,function() {firing = false;_fire();})*/          /*fn的参数就是形参func*/          /*func方法是用来通知上个动画结束,下个动画运行的重要function*/          //func的作用是用来通知动画执行结束,并继续执行下一个动画          const func=function() {            next();          }          fn(func);        }      }      //省略type      const queue=function(element, options, callback, ) {        //模仿从数据缓存中得到的队列,直接写Queue.push也行        const Queue=dataPriv.get("queue")        //向动画队列中添加doAnimation触发器        Queue.push(function(func) {          //doAnimation          callback(element, options, func);        });        //如果没有动画在运行,运行动画        //动画锁inprogress        if(Queue[0]!=='inprogress'){          dequeue()        }      }      /*动画*/      const animation = function(element,options) {        const doAnimation = function(element, options, func) {          // const width = options.width          /*===这里面定义了动画的算法,也就是Animation实现的地方===*/          // 默认动画时长2s          // element.style.transitionDuration = '400ms';          // element.style.width =  width + 'px';          /*监听单个动画完结*/          //transitionend 事件在 CSS 完成过渡后触发          // element.addEventListener('transitionend', function() {          //   func()          // });          //动画的默认属性          let optall={            complete:function(){},            old:false,            duration: 400,            easing: undefined,            queue:"fx",          }          let anim=Animation(element, options, optall,func)        }        //每调用一次animation,就入一次队        return queue(element, options,doAnimation,);      }      //为chenQuery的fn和prototype原型属性 赋 animate属性      chenQuery.fn = chenQuery.prototype = {        //也可以直接把animation里的代码放这里来,但这样就太长了,降低了可读性        animate: function(options) {          animation(this.element, options);          //注意返回的是this,也就是$("#A"),这样就能继续调用animate方法          // 也就是链式调用          return this;        }      }      //为chenQuery的fn属性添加init方法      const init = chenQuery.fn.init = function(selector) {        // ["#A", "A",groups: undefined,index: 0,input: "#A"]        const match = rquickExpr.exec(selector);        //这边默认是只找id的元素        const element = document.getElementById(match[1])        //this指chenQuery.fn.init方法        //为该方法添加element属性        this.element = element;        //返回chenQuery.fn.init        return this;      }      //挺绕的,再将init的原型等于chenQuery.fn方法      init.prototype = chenQuery.fn;      //chenQuery本身是一个function(){}      // chenQuery{      //init能调用fn,fn能调用init      //   fn:{      //     animate:function(){},      //     init:function(){},      //     // init.prototype=fn      //   },      //   prototype:{      //     animate:function(){},      //   }      // }      return chenQuery;    }());  const A = document.querySelector('#A');  //在异步调用中,进行同步调用  //动画是异步的  A.onclick = function() {    //就是连续调用animation.add()    $('#A').animate({      'width': '500'    }).animate({      'width': '300'    }).animate({      'width': '1000'    });  };</script></body></html>

运行结果:

解析:
(1)单个动画本身也有循环,也就是利用requestAnimationFrame循环动画帧,从而绘制动画
(2)当percent <1 时,即动画运行时间小于总体时间,就不断运行动画帧;当percent =1 时,表示动画结束,通知动画队列,运行下个动画,如此循环即可


(完)