前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >jQuery之模拟实现$().animate()(下)

jQuery之模拟实现$().animate()(下)

作者头像
进击的小进进
发布2019-09-05 17:08:11
7260
发布2019-09-05 17:08:11
举报

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

逻辑图:

实现:

代码语言:javascript
复制
<!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 时,表示动画结束,通知动画队列,运行下个动画,如此循环即可


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档