Dojo动画原理解析

 dojo中动画部分分为两部分:dojo/_base/fx, dojo/fx。dojo/_base/fx部分是dojo动画的基石,里面有两个底层API:animateProperty、anim和两个常用动画:fadeIn、fadeOut(类似jQuery中的show、hide)。dojo/fx中有两个复合动画:chain(类似jQuery中的动画队列)、combine和三个动画函数:wipeIn、wipeOut、slideTo。

  dojo中动画的原理与jquery类似,都是根据setInterval计算当前时间与初试时间差值与总时间的半分比来确定元素动画属性的当前计算值:

percent = (Date.now() - startTime) / duration;
value = (end - start) * percent + start;

 接下来我们会一步步的演化,慢慢接近dojo API

  首先我们实现一个动画函数,他接受以下参数:

  • node: 动画元素
  • prop: 动画属性
  • start: 动画起始值
  • end:  动画结束值
  • duration: 动画执行时间
  • interval:动画间隔
function Animate(node, prop, start, end, duration, interval) {
 var startTime = Date.now();

 var timer = setInterval(function(){
  var percent = (Date.now() - startTime) / duration;
  percent = percent < 0 ? 0 : percent;
  percent = percent > 1 ? 1 : percent;
  var v = (end - start) * percent + start;

  node.style[prop] = v;

  if (percent >= 1) {
   clearInterval(timer);
  }
 }, interval);
}

 示例:

  

  dojo中所有的动画函数都返回一个Animation的实例:Animation拥有一系列的属性和动画控制方法,下面我们简单的实现play和stop方法:

function Animate(node, prop, start, end, duration, interval/*, delay*/) {
      var timer = null;
      var startTime =0;

      function startTimer() {
        timer = setInterval(function(){
          var percent = (Date.now() - startTime) / duration;
          percent = percent < 0 ? 0 : percent;
          percent = percent > 1 ? 1 : percent;

          var v = (end - start) * percent + start;

          node.style[prop] = isFinite(v) ? v /*+ 'px'*/ : v;

          if (percent >= 1) {
            clearInterval(timer);
            timer = null;
          }
        }, interval);
      }

      function stopTimer() {
        if (timer) {
          clearInterval(timer);
          timer = null;
        }
      }

      return {
        play: function() {
          if (startTime === 0) {
            startTime = Date.now();
          }

          startTimer();
        },
        stop: function() {
          stopTimer();
        }
      }
    }

 这里将上文中Animate函数放入startTimer函数中,增加stopTimer用来移除定时器。

示例:

 

  下面我们要支持延时和暂停功能,实现延迟的方法在于使用setTimeout设置延时启动时间,对于暂停功能,我们需要记录所有的暂停时间,在计算当前百分比时减去所有的暂停时间。

function Animate(node, prop, start, end, duration, interval, delay) {
      var timer = null;
      var startTime =0;
      var delayTimer = null;

      var paused = false;
      var pauseStartTime = null;
      var pausedTime = 0;//记录所有的暂停时间

      function startTimer() {
        timer = setInterval(function(){
          var percent = (Date.now() - startTime - pausedTime) / duration;减去暂停消耗的时间
          percent = percent < 0 ? 0 : percent;
          percent = percent > 1 ? 1 : percent;

          var v = (end - start) * percent + start;

          node.style[prop] = isFinite(v) ? v /*+ 'px'*/ : v;

          if (percent >= 1) {
            stopTimer();
          }
        }, interval);
      }

      function stopTimer() {
        if (timer) {
          clearInterval(timer);
          timer = null;
        }
      }

      function clearDelayTimer() {
        clearTimeout(delayTimer);
        delayTimer = null;
      }

      return {
        play: function() {
          if (startTime === 0) {
            startTime = Date.now();
          }

          if (paused) {
            pausedTime += Date.now() - pauseStartTime;计算暂停时间
            startTimer();
            paused = false;
          } else if (isFinite(delay)) {
            delayTimer = setTimeout(function() {
              clearDelayTimer();
              startTime = Date.now();
              startTimer();
            }, delay); //delay延迟启动
          } else {
            startTimer();
          }
        },
        pause: function() {
          paused = true;
          if (delayTimer) {
            clearDelayTimer();
          } else {
            stopTimer();
            pauseStartTime = Date.now();记录本次暂停起始时间
          }
        },
        stop: function() {
          stopTimer();
        }
      }
    }

示例:

 

  dojo/fx.animateProperty中可以设置多个动画属性,实现方式不难,只需要在每次动画计算时依次计算各个动画属性即可。

function Animate(node, props, duration, interval, delay) {
      var timer = null;
      var startTime =0;
      var delayTimer = null;

      var paused = false;
      var pauseStartTime = null;
      var pausedTime = 0;

      function startTimer() {
        timer = setInterval(function(){
          var percent = (Date.now() - startTime - pausedTime) / duration;
          percent = percent < 0 ? 0 : percent;
          percent = percent > 1 ? 1 : percent;
          for (var p in props) {
            var prop = props[p];
            node.style[p] = ((prop.end - prop.start) * percent + prop.start) + (prop.units ? prop.units : '');
          }

          if (percent >= 1) {
            stopTimer();
          }
        }, interval);
      }

      function stopTimer() {
        if (timer) {
          clearInterval(timer);
          timer = null;
        }
      }

      function clearDelayTimer() {
        clearTimeout(delayTimer);
        delayTimer = null;
      }

      return {
        play: function() {
          if (startTime === 0) {
            startTime = Date.now();
          }

          if (paused) {
            pausedTime += Date.now() - pauseStartTime;
            startTimer();
            paused = false;
          } else if (isFinite(delay)) {
            delayTimer = setTimeout(function() {
              clearDelayTimer();
              startTime = Date.now();
              startTimer();
            }, delay);
          } else {
            startTimer();
          }
        },
        pause: function() {
          paused = true;
          if (delayTimer) {
            clearDelayTimer();
          } else {
            stopTimer();
            pauseStartTime = Date.now();
          }
        },
        stop: function() {
          stopTimer();
        }
      }
    }

    var btnPlay = document.getElementById('btnPlay');
    var n = document.getElementById('anim');
    var anim = Animate(n, {
      opacity: {start: 0.3, end: 1},
      width: {start:50, end: 500, units: 'px'}
    }, 5000, 25, 1000);
    btnPlay.onclick = function() {
      anim.play();
    }

    btnPause = document.getElementById('btnPause');
    btnPause.onclick = function() {
      anim.pause();
    }

  翻看dojo代码(dojo/_base/fx line:567)我们会发现,在进行动画之前dojo对一些动画属性做了预处理:

  • 针对width/height动画时,元素本身inline状态的处理
  • 对于Opacity的处理,IE8以下在style中设置滤镜
  • 对于颜色动画的处理

 

  下面我们进行的是事件点的添加,在dojo的实际源码中,回调事件的实现是通过实例化一个dojo/Evented对象来实现的,dojo/Evented是dojo整个事件驱动编程的基石,凡是拥有回调事件的对象都是它的实例。dojo/Evented的核心是dojo/on和dojo/aspect, 这两部分的解释可以看一下我的这几篇文章:

  Javascript事件机制兼容性解决方案

  dojo/aspect源码解析

  Javascript aop(面向切面编程)之around(环绕)

    这里我们将事件回调挂载到实例上

function Animate(node, props, duration, interval, delay, callbacks) {
      var timer = null;
      var startTime =0;
      var delayTimer = null;
      var percent = null;

      var stopped = false;
      var ended = false;

      var paused = false;
      var pauseStartTime = null;
      var pausedTime = 0;

      function startTimer() {
        timer = setInterval(function(){
          if (!percent) {
            callbacks.onBegin ? callbacks.onBegin() : null;
          }

          percent = (Date.now() - startTime - pausedTime) / duration;
          percent = percent < 0 ? 0 : percent;
          percent = percent > 1 ? 1 : percent;
          for (var p in props) {
            var prop = props[p];
            node.style[p] = ((prop.end - prop.start) * percent + prop.start) + (prop.units ? prop.units : '');
          }

          callbacks.onAnimate ? callbacks.onAnimate() : null;

          if (percent >= 1) {
            stopTimer();
            ended = true;
            callbacks.onEnd ? callbacks.onEnd() : null;
          }
        }, interval);
      }

      function stopTimer() {
        if (timer) {
          clearInterval(timer);
          timer = null;
        }
      }

      function clearDelayTimer() {
        clearTimeout(delayTimer);
        delayTimer = null;
      }

      return {
        play: function() {
          if (ended) {
            return;
          }
          if (startTime === 0) {
            startTime = Date.now();

            callbacks.beforeBegin ? callbacks.beforeBegin() : null;
          }

          if (paused) {
            pausedTime += Date.now() - pauseStartTime;
            startTimer();
            paused = false;
          } else if (isFinite(delay)) {
            delayTimer = setTimeout(function() {
              clearDelayTimer();
              startTime = Date.now();
              startTimer();
            }, delay);
          } else {
            startTimer();
          }

          callbacks.onPlay ? callbacks.onPlay() : null;
        },
        pause: function() {
          paused = true;
          if (delayTimer) {
            clearDelayTimer();
          } else {
            stopTimer();
            pauseStartTime = Date.now();
          }

          callbacks.onPause ? callbacks.onPause() : null;
        },
        stop: function() {
          stopTimer();
          stopped = true;
          callbacks.onStop ? callbacks.onStop() : null;
        }
      }
    }

dojo/fx中最重要的两个函数就是chain和combine,chain函数允许我们一次执行一系列动画与jQuery中动画队列的功能类似。由于每个Animation实例都拥有onEnd事件,所以chain函数的实现原理就是在每个动画结束后,调用下一个动画的play函数。要模仿这个功能关键是如果在onEnd函数执行后绑定play函数。dojo中使用aspect.after方法,这里我们简单实现:为Function.prototype添加after方法:

Function.prototype.after = function(fn) {
      var self = this;
      return function() {
        var results = self.apply(this, arguments);
        fn.apply(this, [results]);
      }
    }

还有一个问题就是,上文中利用闭包的实现方式,所有对象的play方法都共享一套变量,在多个实例时有很大问题,所以从现在开始我们使用对象方式构造Animate类。

 下一步就是combine,combine允许多个动画联动。combine的实现原理比较简单,依次调用Animation数组中的各对象的方法即可。

Function.prototype.after = function(fn) {
      var self = this;
      return function() {
        var results = self.apply(this, arguments);
        fn.apply(this, [results]);
      }
    }

    function Animate(node, props, duration, interval, delay) {
      this.node = node;
      this.props = props;
      this.duration = duration;
      this.interval = interval;
      this.delay = delay;

      this.timer = null;
      this.startTime = 0;
      this.delayTimer = null;
      this.percent = null;

      this.stopped = false;
      this.ended = false;

      this.paused = false;
      this.pauseStartTime = null;
      this.pausedTime = 0;
    }

    Animate.prototype._startTimer = function() {
      var self = this;
      this.timer = setInterval(function() {
        if (!self.percent) {
          self.onBegin ? self.onBegin() : null;
        }

        var percent = (Date.now() - self.startTime - self.pausedTime) / self.duration;
        percent = percent < 0 ? 0 : percent;
        percent = percent > 1 ? 1 : percent;

        self.percent = percent;

        for (var p in self.props) {
          var prop = self.props[p];
          self.node.style[p] = ((prop.end - prop.start) * percent + prop.start) + (prop.units ? prop.units : '');
        }

        self.onAnimate ? self.onAnimate() : null;

        if (self.percent >= 1) {
          self._stopTimer();
          self.ended = true;
          self.onEnd ? self.onEnd() : null;
        }
      }, this.interval);
    };

    Animate.prototype._stopTimer = function() {
      if (this.timer) {
        clearInterval(this.timer);
        this.timer = null;
      }
    };

    Animate.prototype._clearDelayTimer = function() {
      clearTimeout(this._delayTimer);
      this._delayTimer = null;
    };

    Animate.prototype.play = function() {
      if (this.ended) {
        return;
      }

      if (this.startTime === 0) {
        this.startTime = Date.now();

        this.beforeBegin ? this.beforeBegin() : null;
      }

      if (this.paused) {
        this.pausedTime += Date.now() - this.pauseStartTime;
        this._startTimer();
        this.paused = false;
      } else if (isFinite(this.delay)) {
        var self = this;
        this._delayTimer = setTimeout(function() {
          self._clearDelayTimer();
          self.startTime = Date.now();
          self._startTimer();
        }, this.delay);
      } else {
        this._startTimer();
      }

      this.onPlay ? this.onPlay() : null;
    };

    Animate.prototype.pause = function() {
      this.paused = true;
      if (this._delayTimer) {
        this._clearDelayTimer();
      } else {
        this._stopTimer();
        this.pauseStartTime = Date.now();
      }

      this.onPause ? this.onPause() : null;
    };

    Animate.prototype.stop = function() {
      this._stopTimer();
      this.stopped = true;
      this.onStop ? this.onStop() : null;
    }

    var btnPlay = document.getElementById('btnPlay');
    var n = document.getElementById('anim');
    var anim1 = new Animate(n, {
      opacity: {start: 0, end: 1},
      width: {start:50, end: 500, units: 'px'}
    }, 5000, 25, 1000);
    var anim2 = new Animate(n, {
      // opacity: {start: 1, end: 0.3},
      height: {start:50, end: 500, units: 'px'}
    }, 5000, 25, 1000);

    var anim3 = new Animate(n, {
      opacity: {start: 1, end: 0.3},
      height: {start:500, end: 50, units: 'px'}
    }, 5000, 25, 1000);

    var anim = combine([anim1, anim2]);
    // anim = chain([anim, anim3]);

    btnPlay.onclick = function() {
      anim.play();
    }

    btnPause = document.getElementById('btnPause');
    btnPause.onclick = function() {
      anim.pause();
    }

    function combine(anims) {
      var anim = {
        play: function() {
          for (var i = 0, len = anims.length; i < len; i++) {
            anims[i].play();
          }
        },
        pause: function() {
          for (var i = 0, len = anims.length; i < len; i++) {
            anims[i].pause();
          }
        },
        stop: function() {
          for (var i = 0, len = anims.length; i < len; i++) {
            anims[i].stop();
          }
        }
      };

      return anim;
    }

    function chain(anims) {
      var index = 0;
      for (var i = 0, len = anims.length; i < len; i++) {
        var a1 = anims[i];
        var a2 = anims[i + 1];
        if (a2) {
          a1.onEnd = a1.onEnd ? a1.onEnd.after(function() {
            index++;
            anims[index].play();
          }) : (function() {}).after(function() {
            index++;
            anims[index].play();
          });
        }
      }

      var anim = {
        play: function() {
          anims[index].play();
        },
        pause: function() {
          anims[index].pause();
        },
        stop: function() {
          anims[index].stop();
        }
      };

      return anim;
    }

  dojo中chain、combine两个函数返回的对象跟Animation拥有同样的方法和属性,这也意味着利用这两个函数我们可以构造出更复杂的动画:

var anim1 = new Animate(n, {
      opacity: {start: 0, end: 1},
      width: {start:50, end: 500, units: 'px'}
    }, 5000, 25, 1000);
    var anim2 = new Animate(n, {
      // opacity: {start: 1, end: 0.3},
      height: {start:50, end: 500, units: 'px'}
    }, 5000, 25, 1000);

    var anim3 = new Animate(n, {
      opacity: {start: 1, end: 0.3},
      height: {start:500, end: 50, units: 'px'}
    }, 5000, 25, 1000);

    var anim = combine([anim1, anim2]);
    anim = chain([anim, anim3]);

 在此有兴趣的读者可以自行实现。

时间: 2024-10-28 07:51:30

Dojo动画原理解析的相关文章

Skinned Mesh原理解析和一个最简单的实现示例

Skinned Mesh原理解析和一个最简单的实现示例   作者:n5 Email: happyfirecn@yahoo.com.cn Blog: http://blog.csdn.net/n5 2008-10月   Histroy: Version:1.01  Date:2008-11-01        修改了一些不精确的用语 Version:1.00 Date:2008-10-19     讲述骨骼动画的资料很多,但大部分都是针对DX8或DX9的SkinnedMesh进行讲解.我觉得对于骨

Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43536355 大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法,当然也是最常用的一些用法,这些用法足以覆盖我们平时大多情况下的动画需求了.但是,正如上篇文章当中所说到的,属性动画对补间动画进行了很大幅度的改进,之前补间动画可以做到的属性动画也能做到,补间动画做不到的现在属性动画也可以做到了.因此,今天我们就来学习一下属性动画的高级用法,看看如何实现一些补间动画

Photoshop变换复制的原理解析分享

给各位Photoshop软件的使用者们来详细的解析分享一下变换复制的原理. 解析分享:   制作变换复制轨迹 首先要了解的是,变换复制,肯定与变换有关,所谓制作变换复制轨迹,就是对图层先使用快捷键Ctrl+T(下面简写为CT)执行变换操作,再使用CAST执行变换复制,也就是CT+CAST操作.那么,哪些图层可以制作变换复制轨迹? >像素图层-可以 >文字图层-可以 >形状图层-可以 >智能对象-不可以(但可以执行变换复制轨迹,这句话可能有点难以理解,下面会详细说明) >组-可

[IT]JSONP跨域的原理解析

JavaScript是一种在Web开发中经常使用的前端动态脚本技术.在JavaScript中,有一个很重要的安全性限制,被称为"Same-Origin Policy"(同源策略).这一策略对于JavaScript代码能够访问的页面内容做了很重要的限制,即JavaScript 只能访问与包含它的文档在同一域下的内容. JavaScript这个安全策略在进行多iframe或多窗口编程.以及Ajax编程时显得尤为重要.根据这个策略,在baidu.com下的页面中包含的JavaScript代码

Android中微信抢红包插件原理解析及开发思路_Android

一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导致了.或许是网络的原因,而且这个也是最大的原因.但是其他的不可忽略的因素也是要考虑到进去的,比如在手机充电锁屏的时候,我们并不知道有人已经开始发红包了,那么这时候也是让我们丧失了一大批红包的原因.那么关于网络的问题,我们开发者可能用相关技术无法解决(当然在Google和Facebook看来的话,他们

Android代码入侵原理解析(一)

Android代码入侵原理解析(一)           1.代码入侵原理 代码入侵,或者叫代码注入,指的是让目标应用/进程执行指定的代码.代码入侵,可以在应用进行运行过程中进行动态分析,也是对应用进行攻击的一种常见方式.我把代码入侵分为两种类型:静态和动态.静态代码入侵是直接修改相关代码,在应用启动和运行之前,指定代码就已经和应用代码关联起来.动态代码入侵是应用启动之后,控制应用运行进程,动态加载和运行指定代码. 2.静态代码入侵 静态代码入侵,有直接和间接的手段. 直接手段是修改应用本身代码

秋色园QBlog技术原理解析:性能优化篇:字节、缓存、并发(十二)

文章回顾: 1: 秋色园QBlog技术原理解析:开篇:整体认识(一) --介绍整体文件夹和文件的作用 2: 秋色园QBlog技术原理解析:认识整站处理流程(二) --介绍秋色园业务处理流程 3: 秋色园QBlog技术原理解析:UrlRewrite之无后缀URL原理(三) --介绍如何实现无后缀URL 4: 秋色园QBlog技术原理解析:UrlRewrite之URL重定向体系(四) --介绍URL如何定位到处理程序 5: 秋色园QBlog技术原理解析:Module之页面基类设计(五) --介绍创建

秋色园QBlog技术原理解析:性能优化篇:access的并发极限及超级分库分散并发方案(十六)

上节回顾:   上节 秋色园QBlog技术原理解析:性能优化篇:数据库文章表分表及分库减压方案(十五) 中, 介绍了 秋色园QBlog 在性能优化方面,从技术的优化手段,开始步入数据库设计优化,并从数据的使用情况上进行了分析,从而将文章内容进行分离,得到新的分表,由于内容比较大,进而分了库,达到一种基础减压.   本节内容:   本节将介绍秋色园 QBlog 的Super分库方案,以及何以如此Super分库的原因.   描述说明:   在进行上了上节的分库方案后,虽然感觉一度秋色园QBlog的访

SQL Server 内存数据库原理解析

原文:SQL Server 内存数据库原理解析 前言 关系型数据库发展至今,细节上以做足文章,在寻求自身突破发展的过程中,内存与分布式数据库是当下最流行的主题,这与性能及扩展性在大数据时代的需求交相辉映.SQL Server作为传统的数据库也在最新发布版本SQL Server 2014中提供了新利器 SQL Server In-Memory OLTP(Hekaton),使得其在OLTP系统中的性能有了几十倍甚至上百倍的性能提升,本篇文章为大家探究一二.         大数据时代的数据如何组织应